Skip to content
Merged
12 changes: 11 additions & 1 deletion src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<ShellBarComponent />
<SearchParamToggleVisibility
shouldBeVisible={(params) => {
if (params === undefined) return true;
if (params.get('showHeaderBar') === null) return true;
return params?.get('showHeaderBar') === 'true';
}}
>
<ShellBarComponent />
</SearchParamToggleVisibility>

<Router>
<SentryRoutes>
<Route path="/mcp" element={<GlobalProviderOutlet />}>
Expand Down
58 changes: 58 additions & 0 deletions src/components/Core/BetaButton.tsx
Original file line number Diff line number Diff line change
@@ -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<ButtonDomRef>(null);
const betaPopoverRef = useRef<PopoverDomRef>(null);
const { t } = useTranslation();

const onBetaClick = () => {
if (betaButtonRef.current) {
betaPopoverRef.current!.opener = betaButtonRef.current;
setBetaPopoverOpen(!betaPopoverOpen);
}
};

return (
<>
<Button ref={betaButtonRef} className={styles.betaButton} onClick={onBetaClick}>
<span className={styles.betaContent}>
<Icon name="information" className={styles.betaIcon} />
<span className={styles.betaText}>{t('ShellBar.betaButton')}</span>
</span>
<BetaPopover open={betaPopoverOpen} setOpen={setBetaPopoverOpen} popoverRef={betaPopoverRef} />
</Button>
</>
);
}

const BetaPopover = ({
open,
setOpen,
popoverRef,
}: {
open: boolean;
setOpen: (arg0: boolean) => void;
popoverRef: RefObject<PopoverDomRef | null>;
}) => {
const { t } = useTranslation();

return (
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
<Text
style={{
padding: '1rem',
maxWidth: '250px',
fontFamily: ThemingParameters.sapFontFamily,
}}
>
{t('ShellBar.betaButtonDescription')}
</Text>
</Popover>
);
};
170 changes: 170 additions & 0 deletions src/components/Core/FeedbackButton.tsx
Original file line number Diff line number Diff line change
@@ -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<PopoverDomRef>(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<ButtonDomRef, ButtonClickEventDetail>) => {
feedbackPopoverRef.current!.opener = e.target;
setFeedbackPopoverOpen(!feedbackPopoverOpen);
};

const onFeedbackMessageChange = (event: Ui5CustomEvent<TextAreaDomRef, { value: string; previousValue: string }>) => {
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 (
<>
<Button icon="feedback" tooltip="Feedback" design={ButtonDesign.Transparent} onClick={onFeedbackClick} />
<FeedbackPopover
open={feedbackPopoverOpen}
setOpen={setFeedbackPopoverOpen}
popoverRef={feedbackPopoverRef}
setRating={setRating}
rating={rating}
feedbackMessage={feedbackMessage}
feedbackSent={feedbackSent}
onFeedbackSent={onFeedbackSent}
onFeedbackMessageChange={onFeedbackMessageChange}
/>
</>
);
}

const FeedbackPopover = ({
open,
setOpen,
popoverRef,
setRating,
rating,
onFeedbackSent,
feedbackMessage,
onFeedbackMessageChange,
feedbackSent,
}: {
open: boolean;
setOpen: (arg0: boolean) => void;
popoverRef: RefObject<PopoverDomRef | null>;
setRating: Dispatch<SetStateAction<number>>;
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 (
<>
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
<div
style={{
padding: '1rem',
width: '250px',
}}
>
{!feedbackSent ? (
<Form headerText={t('ShellBar.feedbackHeader')}>
<FormGroup>
<FormItem labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackRatingLabel')}</Label>}>
<RatingIndicator value={rating} max={5} onChange={onRatingChange} />
</FormItem>
<FormItem
className="formAlignLabelStart"
labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackMessageLabel')}</Label>}
>
<TextArea
value={feedbackMessage}
placeholder={t('ShellBar.feedbackPlaceholder')}
rows={5}
onInput={onFeedbackMessageChange}
/>
</FormItem>
<FormItem>
<Button design="Emphasized" onClick={() => onFeedbackSent()}>
{t('ShellBar.feedbackButton')}
</Button>
</FormItem>
<FormItem>
<Label style={{ color: 'gray' }}>
{t('ShellBar.feedbackNotificationText')}
<Link
href="https://github.com/openmcp-project/ui-frontend/issues/new/choose"
target="_blank"
rel="noreferrer"
>
{t('ShellBar.feedbackNotificationAction')}
</Link>
</Label>
</FormItem>
</FormGroup>
</Form>
) : (
<Label>{t('ShellBar.feedbackThanks')}</Label>
)}
</div>
</Popover>
</>
);
};
70 changes: 69 additions & 1 deletion src/components/Core/IntelligentBreadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -58,3 +72,57 @@ export default function IntelligentBreadcrumbs() {
</>
);
}

export function BreadCrumbFeedbackHeader() {
return (
<FlexBox alignItems={FlexBoxAlignItems.Center}>
<IntelligentBreadcrumbs />
<BetaButton />
<FeedbackButton />
<SearchParamToggleVisibility
shouldBeVisible={(params) => {
if (params === undefined) return false;
if (params.get('showHeaderBar') === null) return false;
return params?.get('showHeaderBar') === 'false';
}}
>
<LogoutMenu />
</SearchParamToggleVisibility>
</FlexBox>
);
}

function LogoutMenu() {
const auth = useAuthOnboarding();
const { t } = useTranslation();

const buttonRef = useRef(null);
const [menuIsOpen, setMenuIsOpen] = useState(false);
return (
<>
<Button
ref={buttonRef}
icon="menu2"
onClick={() => {
setMenuIsOpen(true);
}}
/>
<Menu
opener={buttonRef.current}
open={menuIsOpen}
onClose={() => {
setMenuIsOpen(false);
}}
>
<MenuItem
icon="log"
text={t('ShellBar.signOutButton')}
onClick={async () => {
setMenuIsOpen(false);
await auth.logout();
}}
/>
</Menu>
</>
);
}
Loading
Loading