@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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -0
- package/lib/components/Export/components/ExportForm/components/ExportButton/exportButton.js +6 -1
- package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.js +5 -2
- package/lib/components/Index/components/AzulFileDownload/azulFileDownload.js +10 -5
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.js +1 -1
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.js +20 -21
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.d.ts +1 -0
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.js +1 -0
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.d.ts +2 -1
- package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.js +16 -17
- package/lib/components/Layout/components/Header/header.js +2 -1
- package/lib/components/Login/components/Button/types.d.ts +1 -1
- package/lib/components/Login/components/Buttons/buttons.d.ts +2 -0
- package/lib/components/Login/components/Buttons/buttons.js +5 -0
- package/lib/components/Login/components/Buttons/types.d.ts +8 -0
- package/lib/components/Login/components/Buttons/types.js +1 -0
- package/lib/components/Login/components/Section/components/Consent/consent.d.ts +3 -0
- package/lib/components/Login/components/Section/components/Consent/consent.js +14 -0
- package/lib/components/Login/components/Section/components/Consent/consent.styles.d.ts +7 -0
- package/lib/components/Login/components/Section/components/Consent/consent.styles.js +14 -0
- package/lib/components/Login/components/Section/components/Consent/types.d.ts +6 -0
- package/lib/components/Login/components/Section/components/Consent/types.js +1 -0
- package/lib/components/Login/components/Section/components/Warning/warning.d.ts +3 -0
- package/lib/components/Login/components/Section/components/Warning/warning.js +9 -0
- package/lib/components/Login/hooks/useUserConsent/types.d.ts +10 -0
- package/lib/components/Login/hooks/useUserConsent/types.js +1 -0
- package/lib/components/Login/hooks/useUserConsent/useUserConsent.d.ts +2 -0
- package/lib/components/Login/hooks/useUserConsent/useUserConsent.js +24 -0
- package/lib/components/Login/hooks/useUserLogin/types.d.ts +6 -0
- package/lib/components/Login/hooks/useUserLogin/types.js +1 -0
- package/lib/components/Login/hooks/useUserLogin/useUserLogin.d.ts +2 -0
- package/lib/components/Login/hooks/useUserLogin/useUserLogin.js +21 -0
- package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.d.ts +2 -0
- package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.js +6 -0
- package/lib/components/common/LoginDialog/constants.d.ts +6 -0
- package/lib/components/common/LoginDialog/constants.js +21 -0
- package/lib/components/common/LoginDialog/loginDialog.d.ts +2 -0
- package/lib/components/common/LoginDialog/loginDialog.js +27 -0
- package/lib/components/common/LoginDialog/loginDialog.styles.d.ts +3 -0
- package/lib/components/common/LoginDialog/loginDialog.styles.js +50 -0
- package/lib/components/common/LoginDialog/types.d.ts +4 -0
- package/lib/components/common/LoginDialog/types.js +1 -0
- package/lib/config/entities.d.ts +1 -0
- package/lib/providers/loginGuard/common/types.d.ts +18 -0
- package/lib/providers/loginGuard/common/types.js +1 -0
- package/lib/providers/loginGuard/context.d.ts +6 -0
- package/lib/providers/loginGuard/context.js +10 -0
- package/lib/providers/loginGuard/hook.d.ts +9 -0
- package/lib/providers/loginGuard/hook.js +12 -0
- package/lib/providers/loginGuard/provider.d.ts +11 -0
- package/lib/providers/loginGuard/provider.js +55 -0
- package/lib/styles/common/mui/typography.d.ts +1 -0
- package/lib/styles/common/mui/typography.js +7 -0
- package/package.json +1 -1
- package/src/components/Export/components/ExportForm/components/ExportButton/exportButton.tsx +8 -1
- package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.tsx +11 -3
- package/src/components/Index/components/AzulFileDownload/azulFileDownload.tsx +12 -5
- package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.tsx +1 -1
- package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.tsx +16 -15
- package/src/components/Layout/components/Header/components/Content/components/Navigation/constants.ts +1 -0
- package/src/components/Layout/components/Header/components/Content/components/Navigation/navigation.tsx +26 -18
- package/src/components/Layout/components/Header/header.tsx +6 -1
- package/src/components/Login/components/Button/types.ts +1 -1
- package/src/components/Login/components/Buttons/buttons.tsx +22 -0
- package/src/components/Login/components/Buttons/types.ts +9 -0
- package/src/components/Login/components/Section/components/Consent/consent.styles.ts +15 -0
- package/src/components/Login/components/Section/components/Consent/consent.tsx +30 -0
- package/src/components/Login/components/Section/components/Consent/types.ts +10 -0
- package/src/components/Login/components/Section/components/Warning/warning.tsx +24 -0
- package/src/components/Login/hooks/useUserConsent/types.ts +11 -0
- package/src/components/Login/hooks/useUserConsent/useUserConsent.ts +32 -0
- package/src/components/Login/hooks/useUserLogin/types.ts +8 -0
- package/src/components/Login/hooks/useUserLogin/useUserLogin.ts +29 -0
- package/src/components/common/CustomIcon/components/CloseIcon/closeIcon.tsx +17 -0
- package/src/components/common/LoginDialog/constants.ts +33 -0
- package/src/components/common/LoginDialog/loginDialog.styles.ts +51 -0
- package/src/components/common/LoginDialog/loginDialog.tsx +56 -0
- package/src/components/common/LoginDialog/types.ts +4 -0
- package/src/config/entities.ts +1 -0
- package/src/providers/loginGuard/common/types.ts +21 -0
- package/src/providers/loginGuard/context.ts +12 -0
- package/src/providers/loginGuard/hook.ts +14 -0
- package/src/providers/loginGuard/provider.tsx +76 -0
- package/src/styles/common/mui/typography.ts +8 -0
- 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
|
+
}
|
package/package.json
CHANGED
package/src/components/Export/components/ExportForm/components/ExportButton/exportButton.tsx
CHANGED
|
@@ -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={
|
|
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={
|
|
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={()
|
|
100
|
-
|
|
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={()
|
|
58
|
-
setIsRequestPending(true);
|
|
59
|
-
trackFileDownloaded(entityName, relatedEntityId, relatedEntityName);
|
|
60
|
-
run();
|
|
61
|
-
}}
|
|
68
|
+
onClick={() => requireLogin(handleDownloadClick)}
|
|
62
69
|
size="medium"
|
|
63
70
|
/>
|
|
64
71
|
)}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
96
|
+
<DXNavigation
|
|
97
|
+
{...navigationProps}
|
|
98
|
+
testId={NAVIGATION_TEST_ID}
|
|
99
|
+
links={navItemsC}
|
|
100
|
+
/>
|
|
96
101
|
)}
|
|
97
102
|
</Center>
|
|
98
103
|
</Fade>
|
|
@@ -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
|
+
}
|