@databiosphere/findable-ui 21.3.0 → 21.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/lib/components/Export/components/ExportForm/components/ExportButton/exportButton.js +6 -1
  4. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.js +5 -2
  5. package/lib/components/Index/components/AzulFileDownload/azulFileDownload.js +10 -5
  6. package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.js +1 -1
  7. package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.js +20 -21
  8. package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.d.ts +1 -0
  9. package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.js +1 -0
  10. package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.d.ts +2 -1
  11. package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.js +16 -17
  12. package/lib/components/Layout/components/Header/header.js +2 -1
  13. package/lib/components/Login/components/Button/types.d.ts +1 -1
  14. package/lib/components/Login/components/Buttons/buttons.d.ts +2 -0
  15. package/lib/components/Login/components/Buttons/buttons.js +5 -0
  16. package/lib/components/Login/components/Buttons/types.d.ts +8 -0
  17. package/lib/components/Login/components/Buttons/types.js +1 -0
  18. package/lib/components/Login/components/Section/components/Consent/consent.d.ts +3 -0
  19. package/lib/components/Login/components/Section/components/Consent/consent.js +14 -0
  20. package/lib/components/Login/components/Section/components/Consent/consent.styles.d.ts +7 -0
  21. package/lib/components/Login/components/Section/components/Consent/consent.styles.js +14 -0
  22. package/lib/components/Login/components/Section/components/Consent/types.d.ts +6 -0
  23. package/lib/components/Login/components/Section/components/Consent/types.js +1 -0
  24. package/lib/components/Login/components/Section/components/Warning/warning.d.ts +3 -0
  25. package/lib/components/Login/components/Section/components/Warning/warning.js +9 -0
  26. package/lib/components/Login/hooks/useUserConsent/types.d.ts +10 -0
  27. package/lib/components/Login/hooks/useUserConsent/types.js +1 -0
  28. package/lib/components/Login/hooks/useUserConsent/useUserConsent.d.ts +2 -0
  29. package/lib/components/Login/hooks/useUserConsent/useUserConsent.js +24 -0
  30. package/lib/components/Login/hooks/useUserLogin/types.d.ts +6 -0
  31. package/lib/components/Login/hooks/useUserLogin/types.js +1 -0
  32. package/lib/components/Login/hooks/useUserLogin/useUserLogin.d.ts +2 -0
  33. package/lib/components/Login/hooks/useUserLogin/useUserLogin.js +21 -0
  34. package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.d.ts +2 -0
  35. package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.js +6 -0
  36. package/lib/components/common/LoginDialog/constants.d.ts +6 -0
  37. package/lib/components/common/LoginDialog/constants.js +21 -0
  38. package/lib/components/common/LoginDialog/loginDialog.d.ts +2 -0
  39. package/lib/components/common/LoginDialog/loginDialog.js +27 -0
  40. package/lib/components/common/LoginDialog/loginDialog.styles.d.ts +3 -0
  41. package/lib/components/common/LoginDialog/loginDialog.styles.js +50 -0
  42. package/lib/components/common/LoginDialog/types.d.ts +4 -0
  43. package/lib/components/common/LoginDialog/types.js +1 -0
  44. package/lib/config/entities.d.ts +1 -0
  45. package/lib/providers/loginGuard/common/types.d.ts +18 -0
  46. package/lib/providers/loginGuard/common/types.js +1 -0
  47. package/lib/providers/loginGuard/context.d.ts +6 -0
  48. package/lib/providers/loginGuard/context.js +10 -0
  49. package/lib/providers/loginGuard/hook.d.ts +9 -0
  50. package/lib/providers/loginGuard/hook.js +12 -0
  51. package/lib/providers/loginGuard/provider.d.ts +11 -0
  52. package/lib/providers/loginGuard/provider.js +55 -0
  53. package/lib/styles/common/mui/typography.d.ts +1 -0
  54. package/lib/styles/common/mui/typography.js +7 -0
  55. package/package.json +1 -1
  56. package/src/components/Export/components/ExportForm/components/ExportButton/exportButton.tsx +8 -1
  57. package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.tsx +11 -3
  58. package/src/components/Index/components/AzulFileDownload/azulFileDownload.tsx +12 -5
  59. package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.tsx +1 -1
  60. package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.tsx +16 -15
  61. package/src/components/Layout/components/Header/components/Content/components/Navigation/constants.ts +1 -0
  62. package/src/components/Layout/components/Header/components/Content/components/Navigation/navigation.tsx +26 -18
  63. package/src/components/Layout/components/Header/header.tsx +6 -1
  64. package/src/components/Login/components/Button/types.ts +1 -1
  65. package/src/components/Login/components/Buttons/buttons.tsx +22 -0
  66. package/src/components/Login/components/Buttons/types.ts +9 -0
  67. package/src/components/Login/components/Section/components/Consent/consent.styles.ts +15 -0
  68. package/src/components/Login/components/Section/components/Consent/consent.tsx +30 -0
  69. package/src/components/Login/components/Section/components/Consent/types.ts +10 -0
  70. package/src/components/Login/components/Section/components/Warning/warning.tsx +24 -0
  71. package/src/components/Login/hooks/useUserConsent/types.ts +11 -0
  72. package/src/components/Login/hooks/useUserConsent/useUserConsent.ts +32 -0
  73. package/src/components/Login/hooks/useUserLogin/types.ts +8 -0
  74. package/src/components/Login/hooks/useUserLogin/useUserLogin.ts +29 -0
  75. package/src/components/common/CustomIcon/components/CloseIcon/closeIcon.tsx +17 -0
  76. package/src/components/common/LoginDialog/constants.ts +33 -0
  77. package/src/components/common/LoginDialog/loginDialog.styles.ts +51 -0
  78. package/src/components/common/LoginDialog/loginDialog.tsx +56 -0
  79. package/src/components/common/LoginDialog/types.ts +4 -0
  80. package/src/config/entities.ts +1 -0
  81. package/src/providers/loginGuard/common/types.ts +21 -0
  82. package/src/providers/loginGuard/context.ts +12 -0
  83. package/src/providers/loginGuard/hook.ts +14 -0
  84. package/src/providers/loginGuard/provider.tsx +76 -0
  85. package/src/styles/common/mui/typography.ts +8 -0
  86. package/tests/provider.test.tsx +191 -0
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ /**
3
+ * A callback function to be stored and then executed upon successful login.
4
+ */
5
+ export type LoginGuardCallback = () => void;
6
+ /**
7
+ * The shape of the LoginGuard context, provides a function to trigger the
8
+ * login process.
9
+ */
10
+ export interface LoginGuardContextProps {
11
+ requireLogin: (callback?: LoginGuardCallback) => void;
12
+ }
13
+ /**
14
+ * The properties for the LoginGuardProvider component.
15
+ */
16
+ export interface LoginGuardProviderProps {
17
+ children: ReactNode;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { LoginGuardContextProps } from "./common/types";
2
+ /**
3
+ * LoginGuardContext provides a way to trigger a login process. Default value is to
4
+ * call the callback immediately, if specified.
5
+ */
6
+ export declare const LoginGuardContext: import("react").Context<LoginGuardContextProps>;
@@ -0,0 +1,10 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * LoginGuardContext provides a way to trigger a login process. Default value is to
4
+ * call the callback immediately, if specified.
5
+ */
6
+ export const LoginGuardContext = createContext({
7
+ requireLogin: (callback) => {
8
+ callback?.();
9
+ },
10
+ });
@@ -0,0 +1,9 @@
1
+ import { LoginGuardContextProps } from "./common/types";
2
+ /**
3
+ * Custom hook to access the LoginGuard context. This hook returns an object
4
+ * containing the "requireLogin" function, which allows triggering the application's
5
+ * login process.
6
+ *
7
+ * @returns The current LoginGuard context value.
8
+ */
9
+ export declare function useLoginGuard(): LoginGuardContextProps;
@@ -0,0 +1,12 @@
1
+ import { useContext } from "react";
2
+ import { LoginGuardContext } from "./context";
3
+ /**
4
+ * Custom hook to access the LoginGuard context. This hook returns an object
5
+ * containing the "requireLogin" function, which allows triggering the application's
6
+ * login process.
7
+ *
8
+ * @returns The current LoginGuard context value.
9
+ */
10
+ export function useLoginGuard() {
11
+ return useContext(LoginGuardContext);
12
+ }
@@ -0,0 +1,11 @@
1
+ import { LoginGuardProviderProps } from "./common/types";
2
+ /**
3
+ * LoginGuardProvider is responsible for intercepting actions that require user authentication.
4
+ * It provides a "requireLogin" function via context. When a protected action is triggered while the
5
+ * user is unauthenticated, the LoginDialog is displayed. Upon successful authentication, the saved
6
+ * callback is invoked.
7
+ *
8
+ * @param {LoginGuardProviderProps} props - The provider props that include children.
9
+ * @returns The provider component.
10
+ */
11
+ export declare function LoginGuardProvider({ children, }: LoginGuardProviderProps): JSX.Element;
@@ -0,0 +1,55 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { LoginDialog } from "../../components/common/LoginDialog/loginDialog";
3
+ import { useAuthenticationConfig } from "../../hooks/authentication/config/useAuthenticationConfig";
4
+ import { useConfig } from "../../hooks/useConfig";
5
+ import { useAuth } from "../authentication/auth/hook";
6
+ import { LoginGuardContext } from "./context";
7
+ /**
8
+ * LoginGuardProvider is responsible for intercepting actions that require user authentication.
9
+ * It provides a "requireLogin" function via context. When a protected action is triggered while the
10
+ * user is unauthenticated, the LoginDialog is displayed. Upon successful authentication, the saved
11
+ * callback is invoked.
12
+ *
13
+ * @param {LoginGuardProviderProps} props - The provider props that include children.
14
+ * @returns The provider component.
15
+ */
16
+ export function LoginGuardProvider({ children, }) {
17
+ // Dialog open state.
18
+ const [open, setOpen] = useState(false);
19
+ // Use ref to store the callback without triggering re-render.
20
+ const callbackRef = useRef(undefined);
21
+ // Determine if authentication is enabled.
22
+ const authConfig = useAuthenticationConfig();
23
+ // Determine if authentication is required for downloads and exports.
24
+ const { config: { exportsRequireAuth }, } = useConfig();
25
+ // Get the user's authenticated state.
26
+ const { authState: { isAuthenticated }, } = useAuth();
27
+ // If the user authenticates, close dialog then fire and clear callback.
28
+ useEffect(() => {
29
+ if (isAuthenticated) {
30
+ setOpen(false);
31
+ callbackRef.current?.();
32
+ // Clear callback after firing.
33
+ callbackRef.current = undefined;
34
+ }
35
+ }, [isAuthenticated]);
36
+ // Handler to close the dialog.
37
+ const onClose = useCallback(() => {
38
+ setOpen(false);
39
+ // Clear any stored callback.
40
+ callbackRef.current = undefined;
41
+ }, []);
42
+ // Block actions that require authentication, or fire callback if already authenticated.
43
+ const requireLogin = useCallback((cb) => {
44
+ if (authConfig && exportsRequireAuth && !isAuthenticated) {
45
+ callbackRef.current = cb;
46
+ setOpen(true);
47
+ }
48
+ else {
49
+ cb?.();
50
+ }
51
+ }, [authConfig, exportsRequireAuth, isAuthenticated]);
52
+ return (React.createElement(LoginGuardContext.Provider, { value: { requireLogin } },
53
+ children,
54
+ React.createElement(LoginDialog, { open: open, onClose: onClose })));
55
+ }
@@ -1,2 +1,3 @@
1
1
  import { TypographyOwnProps } from "@mui/material";
2
+ export declare const COLOR: Record<string, TypographyOwnProps["color"]>;
2
3
  export declare const VARIANT: Record<string, TypographyOwnProps["variant"]>;
@@ -1,3 +1,10 @@
1
+ export const COLOR = {
2
+ INHERIT: "inherit",
3
+ INK_LIGHT: "ink.light",
4
+ INK_MAIN: "ink.main",
5
+ };
1
6
  export const VARIANT = {
2
7
  INHERIT: "inherit",
8
+ TEXT_BODY_400: "text-body-400",
9
+ TEXT_HEADING_SMALL: "text-heading-small",
3
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "21.3.0",
3
+ "version": "21.4.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -2,6 +2,7 @@ import { Tooltip } from "@mui/material";
2
2
  import React, { ElementType, ReactNode } from "react";
3
3
  import { useDownloadStatus } from "../../../../../../hooks/useDownloadStatus";
4
4
  import { useFileManifestState } from "../../../../../../hooks/useFileManifestState";
5
+ import { useLoginGuard } from "../../../../../../providers/loginGuard/hook";
5
6
  import { ButtonPrimary } from "../../../../../common/Button/components/ButtonPrimary/buttonPrimary";
6
7
 
7
8
  export interface ExportButtonProps {
@@ -19,6 +20,10 @@ export const ExportButton = ({
19
20
  const {
20
21
  fileManifestState: { isLoading },
21
22
  } = useFileManifestState();
23
+
24
+ // Prompt user for login before export, if required.
25
+ const { requireLogin } = useLoginGuard();
26
+
22
27
  return (
23
28
  <Tooltip arrow title={isLoading ? null : downloadStatus.message}>
24
29
  <span>
@@ -26,7 +31,9 @@ export const ExportButton = ({
26
31
  disabled={
27
32
  isLoading || downloadStatus.disabled || downloadStatus.isLoading
28
33
  }
29
- onClick={onClick}
34
+ onClick={() => {
35
+ requireLogin(onClick);
36
+ }}
30
37
  >
31
38
  <span>{children}</span>
32
39
  </Button>
@@ -10,6 +10,7 @@ import React, { useRef } from "react";
10
10
  import { Filters } from "../../../../../../../../common/entities";
11
11
  import { useDownloadStatus } from "../../../../../../../../hooks/useDownloadStatus";
12
12
  import { useFileManifestDownload } from "../../../../../../../../hooks/useFileManifest/useFileManifestDownload";
13
+ import { useLoginGuard } from "../../../../../../../../providers/loginGuard/hook";
13
14
  import { ButtonGroup } from "../../../../../../../common/ButtonGroup/buttonGroup";
14
15
  import { ButtonGroupButton } from "../../../../../../../common/ButtonGroup/components/ButtonGroupButton/buttonGroupButton";
15
16
  import {
@@ -46,6 +47,9 @@ export const FileManifestDownload = ({
46
47
  const isInProgress = (isIdle || isLoading) && !disabled;
47
48
  const isReady = Boolean(manifestURL) || disabled;
48
49
 
50
+ // Prompt user for login before download and copy, if required.
51
+ const { requireLogin } = useLoginGuard();
52
+
49
53
  // Copies file manifest.
50
54
  const copyManifestURL = (url?: string): void => {
51
55
  if (!url) return;
@@ -89,15 +93,19 @@ export const FileManifestDownload = ({
89
93
  action="Download file manifest"
90
94
  disabled={disabled}
91
95
  label={<DownloadIconSmall />}
92
- onClick={downloadManifestURL}
96
+ onClick={() =>
97
+ requireLogin(downloadManifestURL)
98
+ }
93
99
  />,
94
100
  <ButtonGroupButton
95
101
  key="copy"
96
102
  action="Copy file manifest"
97
103
  disabled={disabled}
98
104
  label={<ContentCopyIconSmall />}
99
- onClick={(): void =>
100
- copyManifestURL(manifestURL)
105
+ onClick={() =>
106
+ requireLogin((): void =>
107
+ copyManifestURL(manifestURL)
108
+ )
101
109
  }
102
110
  />,
103
111
  ]}
@@ -1,6 +1,7 @@
1
1
  import { Box } from "@mui/material";
2
2
  import React, { Fragment, useEffect, useRef, useState } from "react";
3
3
  import { useFileLocation } from "../../../../hooks/useFileLocation";
4
+ import { useLoginGuard } from "../../../../providers/loginGuard/hook";
4
5
  import { DownloadIcon } from "../../../common/CustomIcon/components/DownloadIcon/downloadIcon";
5
6
  import { LoadingIcon } from "../../../common/CustomIcon/components/LoadingIcon/loadingIcon";
6
7
  import { IconButton } from "../../../common/IconButton/iconButton";
@@ -29,6 +30,9 @@ export const AzulFileDownload = ({
29
30
  const downloadRef = useRef<HTMLAnchorElement>(null);
30
31
  const [isRequestPending, setIsRequestPending] = useState(false);
31
32
 
33
+ // Prompt user for login before download, if required.
34
+ const { requireLogin } = useLoginGuard();
35
+
32
36
  // Initiates file download when file location request is successful.
33
37
  useEffect(() => {
34
38
  if (!fileUrl) return;
@@ -39,6 +43,13 @@ export const AzulFileDownload = ({
39
43
  setIsRequestPending(false);
40
44
  }, [fileUrl]);
41
45
 
46
+ // Initiates file download when download button is clicked.
47
+ const handleDownloadClick = (): void => {
48
+ setIsRequestPending(true);
49
+ trackFileDownloaded(entityName, relatedEntityId, relatedEntityName);
50
+ run();
51
+ };
52
+
42
53
  return (
43
54
  <Fragment>
44
55
  {isRequestPending ? (
@@ -54,11 +65,7 @@ export const AzulFileDownload = ({
54
65
  data-testid={AZUL_FILE_REQUEST_DOWNLOAD_TEST_ID}
55
66
  disabled={!url}
56
67
  Icon={isLoading ? LoadingIcon : DownloadIcon}
57
- onClick={(): void => {
58
- setIsRequestPending(true);
59
- trackFileDownloaded(entityName, relatedEntityId, relatedEntityName);
60
- run();
61
- }}
68
+ onClick={() => requireLogin(handleDownloadClick)}
62
69
  size="medium"
63
70
  />
64
71
  )}
@@ -78,7 +78,7 @@ export const NavigationMenu = ({
78
78
  >
79
79
  <MPaper variant="menu">
80
80
  <MClickAwayListener onClickAway={onClose}>
81
- <MMenuList>
81
+ <MMenuList component="div">
82
82
  <NavigationMenuItems
83
83
  closeMenu={(): void => {
84
84
  onClose();
@@ -2,9 +2,10 @@ import {
2
2
  Divider,
3
3
  ListItemIcon,
4
4
  ListItemText,
5
+ Link as MLink,
5
6
  MenuItem as MMenuItem,
6
7
  } from "@mui/material";
7
- import { useRouter } from "next/router";
8
+ import Link from "next/link";
8
9
  import React, { Fragment, ReactNode } from "react";
9
10
  import {
10
11
  TEXT_BODY_400,
@@ -38,7 +39,6 @@ export const NavigationMenuItems = ({
38
39
  menuItems,
39
40
  pathname,
40
41
  }: NavLinkMenuProps): JSX.Element => {
41
- const router = useRouter();
42
42
  return (
43
43
  <>
44
44
  {menuItems.map(
@@ -54,8 +54,9 @@ export const NavigationMenuItems = ({
54
54
  url,
55
55
  },
56
56
  i
57
- ) =>
58
- nestedMenuItems ? (
57
+ ) => {
58
+ const isClientSide = isClientSideNavigation(url);
59
+ return nestedMenuItems ? (
59
60
  <NavigationMenu
60
61
  key={i}
61
62
  closeAncestor={closeMenu}
@@ -69,18 +70,17 @@ export const NavigationMenuItems = ({
69
70
  ) : (
70
71
  <Fragment key={i}>
71
72
  <MMenuItem
73
+ component={isClientSide ? Link : MLink}
72
74
  disabled={!url}
73
- onClick={(): void => {
74
- closeMenu();
75
- isClientSideNavigation(url)
76
- ? router.push(url)
77
- : window.open(
78
- url,
79
- target,
80
- REL_ATTRIBUTE.NO_OPENER_NO_REFERRER
81
- );
82
- }}
75
+ href={url}
76
+ onClick={(): void => closeMenu()}
77
+ rel={
78
+ isClientSide
79
+ ? REL_ATTRIBUTE.NO_OPENER
80
+ : REL_ATTRIBUTE.NO_OPENER_NO_REFERRER
81
+ }
83
82
  selected={isNavigationLinkSelected(pathname, selectedPatterns)}
83
+ target={target}
84
84
  >
85
85
  {icon && <ListItemIcon>{icon}</ListItemIcon>}
86
86
  <ListItemText
@@ -100,7 +100,8 @@ export const NavigationMenuItems = ({
100
100
  </MMenuItem>
101
101
  {divider && <Divider />}
102
102
  </Fragment>
103
- )
103
+ );
104
+ }
104
105
  )}
105
106
  </>
106
107
  );
@@ -0,0 +1 @@
1
+ export const NAVIGATION_TEST_ID = "navigation";
@@ -1,5 +1,5 @@
1
- import { Button, Divider } from "@mui/material";
2
- import { useRouter } from "next/router";
1
+ import { Button, Divider, Link as MLink } from "@mui/material";
2
+ import Link from "next/link";
3
3
  import React, { CSSProperties, forwardRef, Fragment, ReactNode } from "react";
4
4
  import { BreakpointKey } from "../../../../../../../../hooks/useBreakpointHelper";
5
5
  import {
@@ -7,6 +7,7 @@ import {
7
7
  REL_ATTRIBUTE,
8
8
  } from "../../../../../../../Links/common/entities";
9
9
  import { isClientSideNavigation } from "../../../../../../../Links/common/utils";
10
+ import { TestIdProps } from "../../../../../../../types";
10
11
  import { SelectedMatch } from "../../../../common/entities";
11
12
  import { HeaderProps } from "../../../../header";
12
13
  import { isNavigationLinkSelected } from "./common/utils";
@@ -28,7 +29,7 @@ export interface NavLinkItem {
28
29
  visible?: Partial<Record<BreakpointKey, boolean>>;
29
30
  }
30
31
 
31
- export interface NavigationProps {
32
+ export interface NavigationProps extends TestIdProps {
32
33
  className?: string;
33
34
  closeAncestor?: () => void;
34
35
  headerProps?: HeaderProps;
@@ -48,12 +49,18 @@ export const Navigation = forwardRef<HTMLDivElement, NavigationProps>(
48
49
  links,
49
50
  pathname,
50
51
  style,
52
+ testId,
51
53
  }: NavigationProps,
52
54
  ref
53
55
  ): JSX.Element {
54
- const router = useRouter();
55
56
  return (
56
- <Links ref={ref} className={className} isMenuIn={isMenuIn} style={style}>
57
+ <Links
58
+ ref={ref}
59
+ className={className}
60
+ data-testid={testId}
61
+ isMenuIn={isMenuIn}
62
+ style={style}
63
+ >
57
64
  {links.map(
58
65
  (
59
66
  {
@@ -65,8 +72,9 @@ export const Navigation = forwardRef<HTMLDivElement, NavigationProps>(
65
72
  url,
66
73
  },
67
74
  i
68
- ) =>
69
- menuItems ? (
75
+ ) => {
76
+ const isClientSide = isClientSideNavigation(url);
77
+ return menuItems ? (
70
78
  <Fragment key={i}>
71
79
  {isMenuIn ? (
72
80
  <NavigationDrawer
@@ -98,17 +106,16 @@ export const Navigation = forwardRef<HTMLDivElement, NavigationProps>(
98
106
  ) : (
99
107
  <Fragment key={i}>
100
108
  <Button
109
+ component={isClientSide ? Link : MLink}
101
110
  disabled={!url}
102
- onClick={(): void => {
103
- closeAncestor?.();
104
- isClientSideNavigation(url)
105
- ? router.push(url)
106
- : window.open(
107
- url,
108
- target,
109
- REL_ATTRIBUTE.NO_OPENER_NO_REFERRER
110
- );
111
- }}
111
+ href={url}
112
+ onClick={(): void => closeAncestor?.()}
113
+ rel={
114
+ isClientSide
115
+ ? REL_ATTRIBUTE.NO_OPENER
116
+ : REL_ATTRIBUTE.NO_OPENER_NO_REFERRER
117
+ }
118
+ target={target}
112
119
  variant={
113
120
  isNavigationLinkSelected(pathname, selectedPatterns)
114
121
  ? "activeNav"
@@ -119,7 +126,8 @@ export const Navigation = forwardRef<HTMLDivElement, NavigationProps>(
119
126
  </Button>
120
127
  {divider && <Divider />}
121
128
  </Fragment>
122
- )
129
+ );
130
+ }
123
131
  )}
124
132
  </Links>
125
133
  );
@@ -21,6 +21,7 @@ import {
21
21
  renderIconButton as renderSearchIconButton,
22
22
  Search,
23
23
  } from "./components/Content/components/Actions/components/Search/search";
24
+ import { NAVIGATION_TEST_ID } from "./components/Content/components/Navigation/constants";
24
25
  import { Navigation as DXNavigation } from "./components/Content/components/Navigation/navigation";
25
26
  import { Slogan } from "./components/Content/components/Slogan/slogan";
26
27
  import { Divider } from "./components/Content/components/Slogan/slogan.styles";
@@ -92,7 +93,11 @@ export const Header = ({ ...headerProps }: HeaderProps): JSX.Element => {
92
93
  <Center>
93
94
  {/* Center navigation */}
94
95
  {isIn.isCenterNavigationIn && (
95
- <DXNavigation {...navigationProps} links={navItemsC} />
96
+ <DXNavigation
97
+ {...navigationProps}
98
+ testId={NAVIGATION_TEST_ID}
99
+ links={navItemsC}
100
+ />
96
101
  )}
97
102
  </Center>
98
103
  </Fade>
@@ -1,4 +1,4 @@
1
- import { ButtonProps } from "@mui/material/Button/Button";
1
+ import { ButtonProps } from "@mui/material";
2
2
  import { BaseComponentProps } from "../../../../theme/common/entities";
3
3
 
4
4
  export type Props = BaseComponentProps & ButtonProps;
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { Button } from "../Button/button";
3
+ import { Props } from "./types";
4
+
5
+ export const Buttons = <P,>({
6
+ className,
7
+ handleLogin,
8
+ providers = [],
9
+ ...props /* Mui ButtonProps */
10
+ }: Props<P>): JSX.Element[] => {
11
+ return providers?.map((provider) => (
12
+ <Button
13
+ key={provider.id}
14
+ className={className}
15
+ endIcon={"icon" in provider && provider.icon}
16
+ onClick={() => handleLogin(provider.id)}
17
+ {...props}
18
+ >
19
+ {provider.name}
20
+ </Button>
21
+ ));
22
+ };
@@ -0,0 +1,9 @@
1
+ import { ButtonProps } from "@mui/material";
2
+ import { ClientSafeProvider } from "next-auth/react";
3
+ import { OAuthProvider } from "../../../../config/entities";
4
+ import { BaseComponentProps } from "../../../../theme/common/entities";
5
+
6
+ export interface Props<P> extends BaseComponentProps, ButtonProps {
7
+ handleLogin: (providerId: string) => void;
8
+ providers?: ClientSafeProvider[] | OAuthProvider<P>[];
9
+ }
@@ -0,0 +1,15 @@
1
+ import styled from "@emotion/styled";
2
+ import { Grid2 } from "@mui/material";
3
+
4
+ export const StyledGrid2 = styled(Grid2)`
5
+ align-items: center;
6
+ align-self: flex-start;
7
+ display: flex;
8
+ gap: 12px;
9
+
10
+ .MuiTypography-text-body-400 {
11
+ .MuiLink-root {
12
+ color: inherit;
13
+ }
14
+ }
15
+ `;
@@ -0,0 +1,30 @@
1
+ import { Checkbox, Typography } from "@mui/material";
2
+ import React from "react";
3
+ import { TEXT_BODY_400 } from "../../../../../../theme/common/typography";
4
+ import { CheckedIcon } from "../../../../../common/CustomIcon/components/CheckedIcon/checkedIcon";
5
+ import { UncheckedErrorIcon } from "../../../../../common/CustomIcon/components/UncheckedErrorIcon/uncheckedErrorIcon";
6
+ import { UncheckedIcon } from "../../../../../common/CustomIcon/components/UncheckedIcon/uncheckedIcon";
7
+ import { BaseComponentProps } from "../../../../../types";
8
+ import { StyledGrid2 } from "./consent.styles";
9
+ import { ConsentProps } from "./types";
10
+
11
+ export const Consent = ({
12
+ children,
13
+ className,
14
+ handleConsent,
15
+ isDisabled,
16
+ isError,
17
+ ...props /* Mui Grid2Props */
18
+ }: BaseComponentProps & ConsentProps): JSX.Element | null => {
19
+ if (isDisabled) return null;
20
+ return (
21
+ <StyledGrid2 className={className} {...props}>
22
+ <Checkbox
23
+ checkedIcon={<CheckedIcon />}
24
+ icon={isError ? <UncheckedErrorIcon /> : <UncheckedIcon />}
25
+ onChange={handleConsent}
26
+ />
27
+ <Typography variant={TEXT_BODY_400}>{children}</Typography>
28
+ </StyledGrid2>
29
+ );
30
+ };
@@ -0,0 +1,10 @@
1
+ import { Grid2Props } from "@mui/material";
2
+ import { ReactNode } from "react";
3
+ import { UseUserConsent } from "../../../../hooks/useUserConsent/types";
4
+
5
+ export interface ConsentProps
6
+ extends Grid2Props,
7
+ Pick<UseUserConsent, "handleConsent">,
8
+ Pick<UseUserConsent["state"], "isDisabled" | "isError"> {
9
+ children: ReactNode;
10
+ }
@@ -0,0 +1,24 @@
1
+ import { Typography, TypographyProps } from "@mui/material";
2
+ import React from "react";
3
+ import { COLOR } from "../../../../../../styles/common/mui/typography";
4
+ import { TEXT_BODY_SMALL_400 } from "../../../../../../theme/common/typography";
5
+ import { BaseComponentProps } from "../../../../../types";
6
+
7
+ export const Warning = ({
8
+ children,
9
+ className,
10
+ ...props /* Mui TypographyOwnProps */
11
+ }: BaseComponentProps & TypographyProps): JSX.Element | null => {
12
+ if (!children) return null;
13
+ return (
14
+ <Typography
15
+ className={className}
16
+ color={COLOR.INK_LIGHT}
17
+ mt={6}
18
+ variant={TEXT_BODY_SMALL_400}
19
+ {...props}
20
+ >
21
+ {children}
22
+ </Typography>
23
+ );
24
+ };
@@ -0,0 +1,11 @@
1
+ import { ChangeEvent } from "react";
2
+
3
+ export interface UseUserConsent {
4
+ handleConsent: (e: ChangeEvent<HTMLInputElement>) => void;
5
+ handleError: (error: boolean) => void;
6
+ state: {
7
+ isDisabled: boolean;
8
+ isError: boolean;
9
+ isValid: boolean;
10
+ };
11
+ }
@@ -0,0 +1,32 @@
1
+ import { ChangeEvent, useCallback, useState } from "react";
2
+ import { useAuthenticationConfig } from "../../../../hooks/authentication/config/useAuthenticationConfig";
3
+ import { UseUserConsent } from "./types";
4
+
5
+ export const useUserConsent = (): UseUserConsent => {
6
+ const authConfig = useAuthenticationConfig();
7
+ const [isDisabled] = useState<boolean>(Boolean(!authConfig?.termsOfService));
8
+ const [isError, setIsError] = useState<boolean>(false);
9
+ const [isValid, setIsValid] = useState<boolean>(false);
10
+
11
+ const handleError = useCallback((error: boolean) => {
12
+ setIsError(error);
13
+ }, []);
14
+
15
+ const handleConsent = useCallback(
16
+ (changeEvent: ChangeEvent<HTMLInputElement>): void => {
17
+ handleError(false);
18
+ setIsValid(changeEvent.target.checked);
19
+ },
20
+ [handleError]
21
+ );
22
+
23
+ return {
24
+ handleConsent,
25
+ handleError,
26
+ state: {
27
+ isDisabled,
28
+ isError,
29
+ isValid,
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,8 @@
1
+ import { ProviderId } from "../../../../providers/authentication/common/types";
2
+ import { UseUserConsent } from "../useUserConsent/types";
3
+
4
+ export interface UseUserLogin
5
+ extends Omit<UseUserConsent, "handleError" | "state"> {
6
+ consentState: Pick<UseUserConsent["state"], "isDisabled" | "isError">;
7
+ handleLogin: (providerId: ProviderId) => void;
8
+ }