Skip to content

Commit 768d96b

Browse files
feat: hide ShellBar when search param showHeaderBar=false (#254)
Co-authored-by: Enrico Kaack <enrico.kaack@sap.com>
1 parent 454e623 commit 768d96b

File tree

9 files changed

+329
-212
lines changed

9 files changed

+329
-212
lines changed

src/AppRouter.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@ import { ShellBarComponent } from './components/Core/ShellBar.tsx';
55
import { SentryRoutes } from './mount.ts';
66
import ProjectPage from './spaces/onboarding/pages/ProjectPage.tsx';
77
import McpPage from './spaces/mcp/pages/McpPage.tsx';
8+
import { SearchParamToggleVisibility } from './components/Helper/FeatureToggleExistance.tsx';
89

910
function AppRouter() {
1011
return (
1112
<>
12-
<ShellBarComponent />
13+
<SearchParamToggleVisibility
14+
shouldBeVisible={(params) => {
15+
if (params === undefined) return true;
16+
if (params.get('showHeaderBar') === null) return true;
17+
return params?.get('showHeaderBar') === 'true';
18+
}}
19+
>
20+
<ShellBarComponent />
21+
</SearchParamToggleVisibility>
22+
1323
<Router>
1424
<SentryRoutes>
1525
<Route path="/mcp" element={<GlobalProviderOutlet />}>

src/components/Core/BetaButton.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { ButtonDomRef, Button, Icon, PopoverDomRef, Popover, Text } from '@ui5/webcomponents-react';
2+
import { useState, useRef, RefObject } from 'react';
3+
import styles from './ShellBar.module.css';
4+
import { useTranslation } from 'react-i18next';
5+
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
6+
import { ThemingParameters } from '@ui5/webcomponents-react-base';
7+
8+
export function BetaButton() {
9+
const [betaPopoverOpen, setBetaPopoverOpen] = useState(false);
10+
const betaButtonRef = useRef<ButtonDomRef>(null);
11+
const betaPopoverRef = useRef<PopoverDomRef>(null);
12+
const { t } = useTranslation();
13+
14+
const onBetaClick = () => {
15+
if (betaButtonRef.current) {
16+
betaPopoverRef.current!.opener = betaButtonRef.current;
17+
setBetaPopoverOpen(!betaPopoverOpen);
18+
}
19+
};
20+
21+
return (
22+
<>
23+
<Button ref={betaButtonRef} className={styles.betaButton} onClick={onBetaClick}>
24+
<span className={styles.betaContent}>
25+
<Icon name="information" className={styles.betaIcon} />
26+
<span className={styles.betaText}>{t('ShellBar.betaButton')}</span>
27+
</span>
28+
<BetaPopover open={betaPopoverOpen} setOpen={setBetaPopoverOpen} popoverRef={betaPopoverRef} />
29+
</Button>
30+
</>
31+
);
32+
}
33+
34+
const BetaPopover = ({
35+
open,
36+
setOpen,
37+
popoverRef,
38+
}: {
39+
open: boolean;
40+
setOpen: (arg0: boolean) => void;
41+
popoverRef: RefObject<PopoverDomRef | null>;
42+
}) => {
43+
const { t } = useTranslation();
44+
45+
return (
46+
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
47+
<Text
48+
style={{
49+
padding: '1rem',
50+
maxWidth: '250px',
51+
fontFamily: ThemingParameters.sapFontFamily,
52+
}}
53+
>
54+
{t('ShellBar.betaButtonDescription')}
55+
</Text>
56+
</Popover>
57+
);
58+
};
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import {
2+
PopoverDomRef,
3+
Ui5CustomEvent,
4+
TextAreaDomRef,
5+
Button,
6+
ButtonDomRef,
7+
Popover,
8+
Form,
9+
FormGroup,
10+
FormItem,
11+
Label,
12+
Link,
13+
RatingIndicator,
14+
TextArea,
15+
} from '@ui5/webcomponents-react';
16+
import { Dispatch, RefObject, SetStateAction, useRef, useState } from 'react';
17+
import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding';
18+
import { useTranslation } from 'react-i18next';
19+
import { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js';
20+
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
21+
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
22+
23+
type UI5RatingIndicatorElement = HTMLElement & { value: number };
24+
25+
export function FeedbackButton() {
26+
const feedbackPopoverRef = useRef<PopoverDomRef>(null);
27+
const [feedbackMessage, setFeedbackMessage] = useState('');
28+
const [feedbackSent, setFeedbackSent] = useState(false);
29+
const [feedbackPopoverOpen, setFeedbackPopoverOpen] = useState(false);
30+
const [rating, setRating] = useState(0);
31+
const { user } = useAuthOnboarding();
32+
33+
const onFeedbackClick = (e: Ui5CustomEvent<ButtonDomRef, ButtonClickEventDetail>) => {
34+
feedbackPopoverRef.current!.opener = e.target;
35+
setFeedbackPopoverOpen(!feedbackPopoverOpen);
36+
};
37+
38+
const onFeedbackMessageChange = (event: Ui5CustomEvent<TextAreaDomRef, { value: string; previousValue: string }>) => {
39+
const newValue = event.target.value;
40+
setFeedbackMessage(newValue);
41+
};
42+
43+
async function onFeedbackSent() {
44+
const payload = {
45+
message: feedbackMessage,
46+
rating: rating.toString(),
47+
user: user?.email,
48+
environment: window.location.hostname,
49+
};
50+
try {
51+
await fetch('/api/feedback', {
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
},
56+
body: JSON.stringify(payload),
57+
});
58+
} catch (err) {
59+
console.log(err);
60+
} finally {
61+
setFeedbackSent(true);
62+
}
63+
}
64+
65+
return (
66+
<>
67+
<Button icon="feedback" tooltip="Feedback" design={ButtonDesign.Transparent} onClick={onFeedbackClick} />
68+
<FeedbackPopover
69+
open={feedbackPopoverOpen}
70+
setOpen={setFeedbackPopoverOpen}
71+
popoverRef={feedbackPopoverRef}
72+
setRating={setRating}
73+
rating={rating}
74+
feedbackMessage={feedbackMessage}
75+
feedbackSent={feedbackSent}
76+
onFeedbackSent={onFeedbackSent}
77+
onFeedbackMessageChange={onFeedbackMessageChange}
78+
/>
79+
</>
80+
);
81+
}
82+
83+
const FeedbackPopover = ({
84+
open,
85+
setOpen,
86+
popoverRef,
87+
setRating,
88+
rating,
89+
onFeedbackSent,
90+
feedbackMessage,
91+
onFeedbackMessageChange,
92+
feedbackSent,
93+
}: {
94+
open: boolean;
95+
setOpen: (arg0: boolean) => void;
96+
popoverRef: RefObject<PopoverDomRef | null>;
97+
setRating: Dispatch<SetStateAction<number>>;
98+
rating: number;
99+
onFeedbackSent: () => void;
100+
feedbackMessage: string;
101+
onFeedbackMessageChange: (
102+
event: Ui5CustomEvent<
103+
TextAreaDomRef,
104+
{
105+
value: string;
106+
previousValue: string;
107+
}
108+
>,
109+
) => void;
110+
feedbackSent: boolean;
111+
}) => {
112+
const { t } = useTranslation();
113+
114+
const onRatingChange = (event: Event & { target: UI5RatingIndicatorElement }) => {
115+
setRating(event.target.value);
116+
};
117+
118+
return (
119+
<>
120+
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
121+
<div
122+
style={{
123+
padding: '1rem',
124+
width: '250px',
125+
}}
126+
>
127+
{!feedbackSent ? (
128+
<Form headerText={t('ShellBar.feedbackHeader')}>
129+
<FormGroup>
130+
<FormItem labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackRatingLabel')}</Label>}>
131+
<RatingIndicator value={rating} max={5} onChange={onRatingChange} />
132+
</FormItem>
133+
<FormItem
134+
className="formAlignLabelStart"
135+
labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackMessageLabel')}</Label>}
136+
>
137+
<TextArea
138+
value={feedbackMessage}
139+
placeholder={t('ShellBar.feedbackPlaceholder')}
140+
rows={5}
141+
onInput={onFeedbackMessageChange}
142+
/>
143+
</FormItem>
144+
<FormItem>
145+
<Button design="Emphasized" onClick={() => onFeedbackSent()}>
146+
{t('ShellBar.feedbackButton')}
147+
</Button>
148+
</FormItem>
149+
<FormItem>
150+
<Label style={{ color: 'gray' }}>
151+
{t('ShellBar.feedbackNotificationText')}
152+
<Link
153+
href="https://github.com/openmcp-project/ui-frontend/issues/new/choose"
154+
target="_blank"
155+
rel="noreferrer"
156+
>
157+
{t('ShellBar.feedbackNotificationAction')}
158+
</Link>
159+
</Label>
160+
</FormItem>
161+
</FormGroup>
162+
</Form>
163+
) : (
164+
<Label>{t('ShellBar.feedbackThanks')}</Label>
165+
)}
166+
</div>
167+
</Popover>
168+
</>
169+
);
170+
};

src/components/Core/IntelligentBreadcrumbs.tsx

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
import { Breadcrumbs, BreadcrumbsDomRef, Ui5CustomEvent } from '@ui5/webcomponents-react';
1+
import {
2+
Breadcrumbs,
3+
BreadcrumbsDomRef,
4+
Button,
5+
FlexBox,
6+
FlexBoxAlignItems,
7+
Menu,
8+
MenuItem,
9+
Ui5CustomEvent,
10+
} from '@ui5/webcomponents-react';
211
import { BreadcrumbsItem } from '@ui5/webcomponents-react/wrappers';
312
import { NavigateOptions, useParams } from 'react-router-dom';
413
import useLuigiNavigate from '../Shared/useLuigiNavigate.tsx';
514
import LandscapeLabel from './LandscapeLabel.tsx';
615
import { useTranslation } from 'react-i18next';
16+
import { FeedbackButton } from './FeedbackButton.tsx';
17+
import { BetaButton } from './BetaButton.tsx';
18+
import { useRef, useState } from 'react';
19+
import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding.tsx';
20+
import { SearchParamToggleVisibility } from '../Helper/FeatureToggleExistance.tsx';
721

822
const PREFIX = '/mcp';
923

@@ -58,3 +72,57 @@ export default function IntelligentBreadcrumbs() {
5872
</>
5973
);
6074
}
75+
76+
export function BreadCrumbFeedbackHeader() {
77+
return (
78+
<FlexBox alignItems={FlexBoxAlignItems.Center}>
79+
<IntelligentBreadcrumbs />
80+
<BetaButton />
81+
<FeedbackButton />
82+
<SearchParamToggleVisibility
83+
shouldBeVisible={(params) => {
84+
if (params === undefined) return false;
85+
if (params.get('showHeaderBar') === null) return false;
86+
return params?.get('showHeaderBar') === 'false';
87+
}}
88+
>
89+
<LogoutMenu />
90+
</SearchParamToggleVisibility>
91+
</FlexBox>
92+
);
93+
}
94+
95+
function LogoutMenu() {
96+
const auth = useAuthOnboarding();
97+
const { t } = useTranslation();
98+
99+
const buttonRef = useRef(null);
100+
const [menuIsOpen, setMenuIsOpen] = useState(false);
101+
return (
102+
<>
103+
<Button
104+
ref={buttonRef}
105+
icon="menu2"
106+
onClick={() => {
107+
setMenuIsOpen(true);
108+
}}
109+
/>
110+
<Menu
111+
opener={buttonRef.current}
112+
open={menuIsOpen}
113+
onClose={() => {
114+
setMenuIsOpen(false);
115+
}}
116+
>
117+
<MenuItem
118+
icon="log"
119+
text={t('ShellBar.signOutButton')}
120+
onClick={async () => {
121+
setMenuIsOpen(false);
122+
await auth.logout();
123+
}}
124+
/>
125+
</Menu>
126+
</>
127+
);
128+
}

0 commit comments

Comments
 (0)