Skip to content

Commit 6de3681

Browse files
authored
Merge branch 'master' into game-quiz2
2 parents b2eb34a + 99db441 commit 6de3681

File tree

20 files changed

+1140
-1318
lines changed

20 files changed

+1140
-1318
lines changed

.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ REACT_APP_URL_SHORTENER_DOMAIN=
1414
# for the correct auth details
1515
REACT_APP_OAUTH2_PROVIDER1=test_admin
1616
REACT_APP_OAUTH2_PROVIDER1_NAME=Test login as admin
17-
REACT_APP_OAUTH2_PROVIDER1_ENDPOINT=http://localhost:8000/login?provider=test&code=admin_code
17+
REACT_APP_OAUTH2_PROVIDER1_ENDPOINT=http://localhost:8000/login/callback?provider=test&code=admin_code
1818
REACT_APP_OAUTH2_PROVIDER2=test_staff
1919
REACT_APP_OAUTH2_PROVIDER2_NAME=Test login as staff
20-
REACT_APP_OAUTH2_PROVIDER2_ENDPOINT=http://localhost:8000/login?provider=test&code=staff_code
20+
REACT_APP_OAUTH2_PROVIDER2_ENDPOINT=http://localhost:8000/login/callback?provider=test&code=staff_code
2121
REACT_APP_OAUTH2_PROVIDER3=test_student
2222
REACT_APP_OAUTH2_PROVIDER3_NAME=Test login as student
23-
REACT_APP_OAUTH2_PROVIDER3_ENDPOINT=http://localhost:8000/login?provider=test&code=student_code
23+
REACT_APP_OAUTH2_PROVIDER3_ENDPOINT=http://localhost:8000/login/callback?provider=test&code=student_code
2424

2525
## LumiNUS example
2626
## the provider ID, must be URL-friendly (must match the backend configuration)

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"i18next": "^23.11.2",
5454
"i18next-browser-languagedetector": "^7.2.1",
5555
"java-slang": "^1.0.13",
56+
"js-cookie": "^3.0.5",
5657
"js-slang": "^1.0.74",
5758
"js-yaml": "^4.1.0",
5859
"konva": "^9.2.0",
@@ -116,6 +117,7 @@
116117
"@types/gapi.client.drive": "^3.0.14",
117118
"@types/google.picker": "^0.0.39",
118119
"@types/jest": "^29.0.0",
120+
"@types/js-cookie": "^3.0.6",
119121
"@types/js-yaml": "^4.0.5",
120122
"@types/lodash": "^4.14.195",
121123
"@types/react": "^18.2.13",

src/commons/application/actions/SessionActions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232

3333
const SessionActions = createActions('session', {
3434
fetchAuth: (code: string, providerId?: string) => ({ code, providerId }),
35+
handleSamlRedirect: (jwtCookie: string) => ({ jwtCookie }),
3536
fetchUserAndCourse: () => ({}),
3637
fetchCourseConfig: () => ({}),
3738
fetchAssessment: (assessmentId: number, assessmentPassword?: string) => ({

src/commons/navigationBar/NavigationBar.tsx

Lines changed: 134 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import { IconName, IconNames } from '@blueprintjs/icons';
1616
import classNames from 'classnames';
1717
import { Location } from 'history';
18-
import { useCallback, useMemo, useState } from 'react';
18+
import React, { useMemo, useState } from 'react';
1919
import { Translation } from 'react-i18next';
2020
import { NavLink, Route, Routes, useLocation } from 'react-router-dom';
2121
import { i18nDefaultLangKeys } from 'src/i18n/i18next';
@@ -42,10 +42,53 @@ export type NavbarEntryInfo = {
4242
hiddenInBreakpoints?: ('xs' | 'sm' | 'md' | 'lg')[]; // hide text in Blueprint breakpoints
4343
};
4444

45-
type CreateNavlinkFunction = (navbarEntry: NavbarEntryInfo) => React.ReactElement;
45+
const MobileHamburger: React.FC<{ navlinks: NavbarEntryInfo[] }> = ({ navlinks }) => {
46+
// Don't render drawer when there are 0 navlinks in it
47+
const [mobileSideMenuOpen, setMobileSideMenuOpen] = useState(false);
48+
const shownNavlinks = navlinks.filter(e => !e.disabled);
49+
const renderDrawer = shownNavlinks.length > 0;
50+
51+
const { courseShortName, courseId } = useSession();
52+
53+
return (
54+
<NavbarGroup align={Alignment.LEFT}>
55+
{renderDrawer && (
56+
<Button
57+
onClick={() => setMobileSideMenuOpen(!mobileSideMenuOpen)}
58+
icon={IconNames.MENU}
59+
large={true}
60+
minimal={true}
61+
/>
62+
)}
63+
<NavLink
64+
className="NavigationBar__link"
65+
to={Constants.playgroundOnly ? '/' : courseId == null ? '/welcome' : `/courses/${courseId}`}
66+
>
67+
<NavbarHeading>
68+
<Button className="app-title" minimal icon={IconNames.SYMBOL_DIAMOND}>
69+
{courseShortName || Constants.sourceAcademyDeploymentName}
70+
</Button>
71+
</NavbarHeading>
72+
</NavLink>
73+
{renderDrawer && (
74+
<Drawer
75+
isOpen={mobileSideMenuOpen}
76+
position="left"
77+
onClose={() => setMobileSideMenuOpen(false)}
78+
title=""
79+
className={Classes.DARK}
80+
style={{ overflowY: 'auto' }}
81+
>
82+
{shownNavlinks.map((entry, i) => (
83+
<MobileNavLink key={i} {...entry} handleClick={() => setMobileSideMenuOpen(false)} />
84+
))}
85+
</Drawer>
86+
)}
87+
</NavbarGroup>
88+
);
89+
};
4690

4791
const NavigationBar: React.FC = () => {
48-
const [mobileSideMenuOpen, setMobileSideMenuOpen] = useState(false);
4992
const { isMobileBreakpoint } = useResponsive();
5093
const location = useLocation();
5194
const {
@@ -66,79 +109,6 @@ const NavigationBar: React.FC = () => {
66109

67110
FocusStyleManager.onlyShowFocusOnTabs();
68111

69-
const createMobileNavlink: CreateNavlinkFunction = useCallback(
70-
navbarEntry => (
71-
<NavLink
72-
to={navbarEntry.to}
73-
className={({ isActive }) =>
74-
classNames(Classes.BUTTON, Classes.MINIMAL, Classes.LARGE, { [Classes.ACTIVE]: isActive })
75-
}
76-
onClick={() => setMobileSideMenuOpen(false)}
77-
key={navbarEntry.text}
78-
>
79-
<Icon icon={navbarEntry.icon} />
80-
<div>
81-
<Translation ns="commons" keyPrefix="navigationBar">
82-
{t =>
83-
t(navbarEntry.text as keyof i18nDefaultLangKeys['commons']['navigationBar'], {
84-
defaultValue: navbarEntry.text
85-
})
86-
}
87-
</Translation>
88-
</div>
89-
{navbarEntry.hasNotifications && (
90-
<NotificationBadge
91-
notificationFilter={filterNotificationsByType(navbarEntry.text)}
92-
disableHover={true}
93-
/>
94-
)}
95-
</NavLink>
96-
),
97-
[setMobileSideMenuOpen]
98-
);
99-
100-
const wrapWithMobileHamburger = (navlinks: (React.ReactElement | null)[]) => {
101-
// Don't render drawer when there are 0 navlinks in it
102-
const nonNullNavlinks = navlinks.filter(e => e !== null);
103-
const renderDrawer = nonNullNavlinks.length > 0;
104-
105-
return (
106-
<NavbarGroup align={Alignment.LEFT}>
107-
{renderDrawer && (
108-
<Button
109-
onClick={() => setMobileSideMenuOpen(!mobileSideMenuOpen)}
110-
icon={IconNames.MENU}
111-
large={true}
112-
minimal={true}
113-
/>
114-
)}
115-
<NavLink
116-
className={classNames('NavigationBar__link', Classes.BUTTON, Classes.MINIMAL)}
117-
to={
118-
Constants.playgroundOnly ? '/' : courseId == null ? '/welcome' : `/courses/${courseId}`
119-
}
120-
>
121-
<Icon icon={IconNames.SYMBOL_DIAMOND} />
122-
<NavbarHeading style={{ paddingBottom: '0px' }}>
123-
{courseShortName || Constants.sourceAcademyDeploymentName}
124-
</NavbarHeading>
125-
</NavLink>
126-
{renderDrawer && (
127-
<Drawer
128-
isOpen={mobileSideMenuOpen}
129-
position="left"
130-
onClose={() => setMobileSideMenuOpen(false)}
131-
title=""
132-
className={Classes.DARK}
133-
style={{ overflowY: 'auto' }}
134-
>
135-
{navlinks}
136-
</Drawer>
137-
)}
138-
</NavbarGroup>
139-
);
140-
};
141-
142112
const fullAcademyNavbarLeftAssessmentsInfo: NavbarEntryInfo[] = useMemo(
143113
() =>
144114
assessmentTypesToNavlinkInfo({
@@ -211,27 +181,29 @@ const NavigationBar: React.FC = () => {
211181

212182
const renderPlaygroundOnlyNavbarLeftDesktop = () => (
213183
<NavbarGroup align={Alignment.LEFT}>
214-
{renderNavlinksFromInfo(playgroundOnlyNavbarLeftInfo, createDesktopNavlink)}
184+
{playgroundOnlyNavbarLeftInfo.map((entry, i) => (
185+
<DesktopNavLink key={i} {...entry} />
186+
))}
215187
</NavbarGroup>
216188
);
217189

218-
const renderPlaygroundOnlyNavbarLeftMobile = () =>
219-
wrapWithMobileHamburger(
220-
renderNavlinksFromInfo(playgroundOnlyNavbarLeftInfo, createMobileNavlink)
221-
);
190+
const renderPlaygroundOnlyNavbarLeftMobile = () => (
191+
<MobileHamburger navlinks={playgroundOnlyNavbarLeftInfo} />
192+
);
222193

223194
const renderFullAcademyNavbarLeftDesktop = () => {
195+
const entries = assessmentTypesToNavlinkInfo({
196+
assessmentTypes,
197+
courseId,
198+
isEnrolledInACourse
199+
});
200+
224201
const desktopNavbarLeftPopoverContent = (
225202
<Navbar>
226203
<NavbarGroup>
227-
{renderNavlinksFromInfo(
228-
assessmentTypesToNavlinkInfo({
229-
assessmentTypes,
230-
courseId,
231-
isEnrolledInACourse
232-
}),
233-
createDesktopNavlink
234-
)}
204+
{entries.map((entry, i) => (
205+
<DesktopNavLink key={i} {...entry} />
206+
))}
235207
</NavbarGroup>
236208
</Navbar>
237209
);
@@ -259,26 +231,31 @@ const NavigationBar: React.FC = () => {
259231
disabled={!enableDesktopPopover}
260232
>
261233
<NavLink
262-
className={classNames('NavigationBar__link', Classes.BUTTON, Classes.MINIMAL, {
263-
[Classes.ACTIVE]: highlightDesktopLogo(location)
264-
})}
234+
className="NavigationBar__link"
265235
to={courseId == null ? '/welcome' : `/courses/${courseId}`}
266236
>
267-
<Icon icon={IconNames.SYMBOL_DIAMOND} />
268-
<NavbarHeading style={{ paddingBottom: '0px' }}>
269-
{courseShortName || Constants.sourceAcademyDeploymentName}
237+
<NavbarHeading>
238+
<Button
239+
className="app-title"
240+
minimal
241+
icon={IconNames.SYMBOL_DIAMOND}
242+
active={highlightDesktopLogo(location)}
243+
>
244+
{courseShortName || Constants.sourceAcademyDeploymentName}
245+
</Button>
270246
</NavbarHeading>
271247
</NavLink>
272248
</Popover>
273-
{renderNavlinksFromInfo(fullAcademyNavbarLeftCommonInfo, createDesktopNavlink)}
249+
{fullAcademyNavbarLeftCommonInfo.map((entry, i) => (
250+
<DesktopNavLink key={i} {...entry} />
251+
))}
274252
</NavbarGroup>
275253
);
276254
};
277255

278-
const renderFullAcademyNavbarLeftMobile = () =>
279-
wrapWithMobileHamburger(
280-
renderNavlinksFromInfo(fullAcademyMobileNavbarLeftInfoWithAssessments, createMobileNavlink)
281-
);
256+
const renderFullAcademyNavbarLeftMobile = () => (
257+
<MobileHamburger navlinks={fullAcademyMobileNavbarLeftInfoWithAssessments} />
258+
);
282259

283260
const commonNavbarRight = (
284261
<NavbarGroup align={Alignment.RIGHT}>
@@ -364,46 +341,63 @@ const playgroundOnlyNavbarLeftInfo: NavbarEntryInfo[] = [
364341
// }
365342
];
366343

367-
export const renderNavlinksFromInfo = (
368-
navbarEntries: NavbarEntryInfo[],
369-
createNavlink: CreateNavlinkFunction
370-
): (React.ReactElement | null)[] =>
371-
navbarEntries.map(entry => {
372-
if (entry.disabled) {
373-
return null;
374-
}
375-
376-
return createNavlink(entry);
377-
});
344+
export const DesktopNavLink: React.FC<NavbarEntryInfo> = props => {
345+
const responsive = useResponsive();
346+
const shouldHide = props.hiddenInBreakpoints?.some(bp => responsive[bp]);
347+
return props.disabled ? null : (
348+
<NavLink
349+
className={({ isActive }) => classNames(isActive && Classes.ACTIVE)}
350+
to={props.to}
351+
key={props.text}
352+
title={props.text}
353+
>
354+
<Button minimal icon={props.icon}>
355+
{!shouldHide && (
356+
<Translation ns="commons" keyPrefix="navigationBar">
357+
{t =>
358+
t(props.text as keyof i18nDefaultLangKeys['commons']['navigationBar'], {
359+
defaultValue: props.text
360+
})
361+
}
362+
</Translation>
363+
)}
364+
</Button>
365+
{props.hasNotifications && (
366+
<NotificationBadge
367+
notificationFilter={filterNotificationsByType(props.text)}
368+
disableHover={true}
369+
/>
370+
)}
371+
</NavLink>
372+
);
373+
};
378374

379-
export const createDesktopNavlink: CreateNavlinkFunction = navbarEntry => (
380-
<NavLink
381-
className={({ isActive }) =>
382-
classNames(Classes.BUTTON, Classes.MINIMAL, {
383-
[Classes.ACTIVE]: isActive
384-
})
385-
}
386-
to={navbarEntry.to}
387-
key={navbarEntry.text}
388-
title={navbarEntry.text}
389-
>
390-
<Icon icon={navbarEntry.icon} />
391-
<div className={classNames(navbarEntry.hiddenInBreakpoints?.map(bp => `hidden-${bp}`))}>
392-
<Translation ns="commons" keyPrefix="navigationBar">
393-
{t =>
394-
t(navbarEntry.text as keyof i18nDefaultLangKeys['commons']['navigationBar'], {
395-
defaultValue: navbarEntry.text
396-
})
397-
}
398-
</Translation>
399-
</div>
400-
{navbarEntry.hasNotifications && (
401-
<NotificationBadge
402-
notificationFilter={filterNotificationsByType(navbarEntry.text)}
403-
disableHover={true}
404-
/>
405-
)}
406-
</NavLink>
407-
);
375+
const MobileNavLink: React.FC<
376+
NavbarEntryInfo & { handleClick?: React.MouseEventHandler<HTMLAnchorElement> }
377+
> = props =>
378+
props.disabled ? null : (
379+
<NavLink
380+
to={props.to}
381+
className={({ isActive }) => classNames(isActive && Classes.ACTIVE)}
382+
onClick={props.handleClick}
383+
key={props.text}
384+
>
385+
<Button minimal large icon={props.icon}>
386+
<Translation ns="commons" keyPrefix="navigationBar">
387+
{t =>
388+
t(props.text as keyof i18nDefaultLangKeys['commons']['navigationBar'], {
389+
defaultValue: props.text
390+
})
391+
}
392+
</Translation>
393+
</Button>
394+
{props.hasNotifications && (
395+
<NotificationBadge
396+
notificationFilter={filterNotificationsByType(props.text)}
397+
disableHover={true}
398+
/>
399+
)}
400+
</NavLink>
401+
);
408402

409403
export default NavigationBar;

0 commit comments

Comments
 (0)