diff --git a/apps/frontend/jest.config.ts b/apps/frontend/jest.config.ts index cd6b3874..1c12e49a 100644 --- a/apps/frontend/jest.config.ts +++ b/apps/frontend/jest.config.ts @@ -13,6 +13,7 @@ export default { '^@root(.*)$': '/src$1', '^@shared(.*)$': '/src/app/shared$1', '^@patients(.*)$': '/src/app/patients$1', + '^@login(.*)$': '/src/app/login$1', }, setupFiles: ['/setupTests.ts'], }; diff --git a/apps/frontend/src/app/app.tsx b/apps/frontend/src/app/app.tsx index 707f3838..912c91bf 100644 --- a/apps/frontend/src/app/app.tsx +++ b/apps/frontend/src/app/app.tsx @@ -8,41 +8,58 @@ import { } from './shared/modals/search/SearchModal'; import NavbarLink from './shared/navbar/link/navbar-link'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { + ReconnectedLoginModal, + ReconnectedLoginModalRef, +} from './login/reconnected-login-modal'; +import { UserAvatar } from './shared/user-avatar'; export function App() { const navigate = useNavigate(); const location = useLocation(); const searchModal = useRef(null); + const reconnectedLoginModal = useRef(null); + + // TODO: Call this when the user is reconnected + const openReconnectedLoginModal = () => { + reconnectedLoginModal.current?.show(); + }; return ( - - - searchModal.current?.show()} - > - navigate('/patient-details')} - active={location.pathname === '/patient-details'} - > - - - - - + location.pathname === '/' ? undefined : ( + + + + searchModal.current?.show()} + > + navigate('/patient-details')} + active={location.pathname === '/patient-details'} + > + + + + + + + + + ) } > + ); } diff --git a/apps/frontend/src/app/login/auth0-provider/auth0-provider.tsx b/apps/frontend/src/app/login/auth0-provider/auth0-provider.tsx new file mode 100644 index 00000000..7f183dc8 --- /dev/null +++ b/apps/frontend/src/app/login/auth0-provider/auth0-provider.tsx @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import React from 'react'; +import { Auth0Provider as Auth0ReactProvider } from '@auth0/auth0-react'; + +type Auth0ProviderProps = { + children: React.ReactNode; +}; + +const AUTH0_CLIENT_ID = + process.env['NODE_ENV'] === 'development' + ? process.env['NX_AUTH0_CLIENT_ID_DEV'] + : process.env['NX_AUTH0_CLIENT_ID_PROD']; + +const AUTH0_REDIRECT_URI = + process.env['NODE_ENV'] === 'development' + ? process.env['NX_AUTH0_REDIRECT_URI_DEV'] + : process.env['NX_AUTH0_REDIRECT_URI_PROD']; + +export const Auth0Provider = (props: Auth0ProviderProps) => { + return ( + + {props.children} + + ); +}; + +export default Auth0Provider; diff --git a/apps/frontend/src/app/login/auth0-provider/index.ts b/apps/frontend/src/app/login/auth0-provider/index.ts new file mode 100644 index 00000000..e40179cc --- /dev/null +++ b/apps/frontend/src/app/login/auth0-provider/index.ts @@ -0,0 +1 @@ +export * from './auth0-provider'; diff --git a/apps/frontend/src/app/login/index.ts b/apps/frontend/src/app/login/index.ts new file mode 100644 index 00000000..3442c953 --- /dev/null +++ b/apps/frontend/src/app/login/index.ts @@ -0,0 +1,3 @@ +export * from './login-page'; +export * from './auth0-provider'; +export * from './reconnected-login-modal'; diff --git a/apps/frontend/src/app/login/login-page/index.ts b/apps/frontend/src/app/login/login-page/index.ts new file mode 100644 index 00000000..a4d80b3a --- /dev/null +++ b/apps/frontend/src/app/login/login-page/index.ts @@ -0,0 +1 @@ +export * from './login-page'; diff --git a/apps/frontend/src/app/login/login-page/login-page.spec.tsx b/apps/frontend/src/app/login/login-page/login-page.spec.tsx new file mode 100644 index 00000000..950ce8d8 --- /dev/null +++ b/apps/frontend/src/app/login/login-page/login-page.spec.tsx @@ -0,0 +1,9 @@ +import { render } from '@testing-library/react'; +import LoginPage from './login-page'; + +describe('Login Page', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/login/login-page/login-page.tsx b/apps/frontend/src/app/login/login-page/login-page.tsx new file mode 100644 index 00000000..0578ea98 --- /dev/null +++ b/apps/frontend/src/app/login/login-page/login-page.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Group, Stack, Text, Title } from '@mantine/core'; +import { Button, Logo } from '@shared'; +import { useAuth0 } from '@auth0/auth0-react'; +import { useNetwork } from '@shared'; +import { OfflineLoginPage } from '../offline-login-page'; +import { IconWifi } from '@tabler/icons'; + +export const LoginPage = () => { + const { loginWithRedirect } = useAuth0(); + const { online } = useNetwork(); + + const handleClickLogin = () => { + loginWithRedirect(); + }; + + if (!online) { + return ; + } else { + return ( + + + + Welcome to SuperVision! + + + + + Your device is online
Please log in to continue to + SuperVision. +
+
+ +
+ ); + } +}; + +export default LoginPage; diff --git a/apps/frontend/src/app/login/offline-login-page/index.ts b/apps/frontend/src/app/login/offline-login-page/index.ts new file mode 100644 index 00000000..4eff0a03 --- /dev/null +++ b/apps/frontend/src/app/login/offline-login-page/index.ts @@ -0,0 +1 @@ +export * from './offline-login-page'; diff --git a/apps/frontend/src/app/login/offline-login-page/offline-login-page.spec.tsx b/apps/frontend/src/app/login/offline-login-page/offline-login-page.spec.tsx new file mode 100644 index 00000000..203005e1 --- /dev/null +++ b/apps/frontend/src/app/login/offline-login-page/offline-login-page.spec.tsx @@ -0,0 +1,14 @@ +import { render } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import OfflineLoginPage from './offline-login-page'; + +describe('Offline Login Page', () => { + it('should render successfully', () => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/login/offline-login-page/offline-login-page.tsx b/apps/frontend/src/app/login/offline-login-page/offline-login-page.tsx new file mode 100644 index 00000000..954783bb --- /dev/null +++ b/apps/frontend/src/app/login/offline-login-page/offline-login-page.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Group, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Button, Logo } from '@shared'; +import { useNavigate } from 'react-router-dom'; +import { IconWifiOff } from '@tabler/icons'; + +export const OfflineLoginPage = () => { + const navigate = useNavigate(); + + const [email, setEmail] = React.useState(''); + const [errorMessage, setErrorMessage] = React.useState( + undefined + ); + + const navigateToPatientDetails = () => { + if (email === '') { + setErrorMessage('Please enter an email address'); + } else if ( + !email.endsWith('@auckland.ac.nz') && + !email.endsWith('@aucklanduni.ac.nz') + ) { + setErrorMessage('Invalid email - please ensure you enter a UoA email'); + } else { + setErrorMessage(undefined); + sessionStorage.setItem('userEmail', email); + navigate('/patient-details'); + } + }; + + return ( + + + + Welcome to SuperVision! + + + + + Your device is offline
Please enter your university email + address to identify yourself. +
+
+ setEmail(event.currentTarget.value)} + error={errorMessage} + className="w-full" + /> + +
+ ); +}; + +export default OfflineLoginPage; diff --git a/apps/frontend/src/app/login/reconnected-login-modal/index.ts b/apps/frontend/src/app/login/reconnected-login-modal/index.ts new file mode 100644 index 00000000..31db1377 --- /dev/null +++ b/apps/frontend/src/app/login/reconnected-login-modal/index.ts @@ -0,0 +1 @@ +export * from './reconnected-login-modal'; diff --git a/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.spec.tsx b/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.spec.tsx new file mode 100644 index 00000000..12bcd94f --- /dev/null +++ b/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.spec.tsx @@ -0,0 +1,9 @@ +import { render } from '@testing-library/react'; +import ReconnectedLoginModal from './reconnected-login-modal'; + +describe('Reconnected Login Modal', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.tsx b/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.tsx new file mode 100644 index 00000000..b899145b --- /dev/null +++ b/apps/frontend/src/app/login/reconnected-login-modal/reconnected-login-modal.tsx @@ -0,0 +1,53 @@ +import { useAuth0 } from '@auth0/auth0-react'; +import { Modal, Text } from '@mantine/core'; +import { Button } from '@shared'; +import React, { + ForwardedRef, + forwardRef, + useImperativeHandle, + useState, +} from 'react'; + +export interface ReconnectedLoginModalRef { + show(): void; +} + +export const ReconnectedLoginModal = forwardRef( + (props, ref: ForwardedRef) => { + const { loginWithPopup } = useAuth0(); + const [opened, setOpened] = useState(false); + + useImperativeHandle(ref, () => ({ + show() { + setOpened(true); + }, + })); + + const login = () => { + setOpened(false); + loginWithPopup(); + }; + + return ( + setOpened(false)} + withCloseButton={false} + closeOnClickOutside={false} + closeOnEscape={false} + centered + classNames={{ + title: 'font-extrabold', + }} + > + Please log in + + + ); + } +); + +export default ReconnectedLoginModal; diff --git a/apps/frontend/src/app/routes/protected-route.tsx b/apps/frontend/src/app/routes/protected-route.tsx new file mode 100644 index 00000000..84b3eb3f --- /dev/null +++ b/apps/frontend/src/app/routes/protected-route.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { useAuth0 } from '@auth0/auth0-react'; +import { Navigate } from 'react-router-dom'; +import { Center, Loader } from '@mantine/core'; +import { useNetwork } from '@shared'; + +type ProtectedRouteProps = { + component: JSX.Element; +}; + +const ProtectedRoute = ({ component }: ProtectedRouteProps): JSX.Element => { + const { isLoading, isAuthenticated } = useAuth0(); + const { online, isLoading: onlineStatusLoading } = useNetwork(); + const userEmail = sessionStorage.getItem('userEmail'); + + if (isLoading || onlineStatusLoading) { + return ( +
+ +
+ ); + } else if (isAuthenticated || (!online && userEmail)) { + return component; + } else { + return ; + } +}; + +export default ProtectedRoute; diff --git a/apps/frontend/src/app/routes/routes.tsx b/apps/frontend/src/app/routes/routes.tsx index 40e6728c..d97e9d34 100644 --- a/apps/frontend/src/app/routes/routes.tsx +++ b/apps/frontend/src/app/routes/routes.tsx @@ -2,12 +2,18 @@ import React from 'react'; import App from 'app/app'; import { Route, Routes as RRRoutes } from 'react-router-dom'; import { PatientDetailsPage } from '@patients'; +import { LoginPage } from '@login'; +import ProtectedRoute from './protected-route'; const Routes = () => { return ( }> - } /> + } /> + } />} + /> ); diff --git a/apps/frontend/src/app/shared/button/button.tsx b/apps/frontend/src/app/shared/button/button.tsx index b8f0640c..8787b9a0 100644 --- a/apps/frontend/src/app/shared/button/button.tsx +++ b/apps/frontend/src/app/shared/button/button.tsx @@ -1,9 +1,22 @@ -import { ButtonProps, Button as MantineButton } from '@mantine/core'; +import { + ButtonProps as MantineButtonProps, + Button as MantineButton, +} from '@mantine/core'; import React from 'react'; import styles from './button.module.scss'; +interface ButtonProps extends MantineButtonProps { + onClick?: () => void; +} + export function Button(props: ButtonProps) { - return ; + return ( + + ); } export default Button; diff --git a/apps/frontend/src/app/shared/hooks/index.ts b/apps/frontend/src/app/shared/hooks/index.ts new file mode 100644 index 00000000..3de1011c --- /dev/null +++ b/apps/frontend/src/app/shared/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-network'; diff --git a/apps/frontend/src/app/shared/hooks/use-network.ts b/apps/frontend/src/app/shared/hooks/use-network.ts new file mode 100644 index 00000000..1c529abe --- /dev/null +++ b/apps/frontend/src/app/shared/hooks/use-network.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; + +/* +Pings google.com to check for internet connection. + */ +const ping = async () => { + if (!navigator.onLine) return false; + + try { + await fetch('https://www.google.com/', { + mode: 'no-cors', + }); + return true; + } catch (error) { + return false; + } +}; + +export const useNetwork = () => { + const [online, setOnline] = useState(navigator.onLine); + const [isLoading, setIsLoading] = useState(false); + + const checkOnline = async () => { + // Ping google.com to check for internet connection (navigator.onLine only checks network connection) + const online = await ping(); + setOnline(online); + }; + + const goOffline = () => setOnline(false); + const goOnline = () => setOnline(true); + + useEffect(() => { + setIsLoading(true); + window.addEventListener('online', goOnline); + window.addEventListener('offline', goOffline); + + checkOnline(); + setIsLoading(false); + return () => { + window.removeEventListener('online', goOnline); + window.removeEventListener('offline', goOffline); + }; + }, []); + + return { online, isLoading }; +}; diff --git a/apps/frontend/src/app/shared/index.ts b/apps/frontend/src/app/shared/index.ts index fea3dd74..90b1bbca 100644 --- a/apps/frontend/src/app/shared/index.ts +++ b/apps/frontend/src/app/shared/index.ts @@ -4,3 +4,4 @@ export * from './header'; export * from './navbar'; export * from './table'; export * from './button'; +export * from './hooks'; diff --git a/apps/frontend/src/app/shared/logo/logo-sm.svg b/apps/frontend/src/app/shared/logo/logo-sm.svg new file mode 100644 index 00000000..aaf0015e --- /dev/null +++ b/apps/frontend/src/app/shared/logo/logo-sm.svg @@ -0,0 +1,438 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/frontend/src/app/shared/logo/logo.tsx b/apps/frontend/src/app/shared/logo/logo.tsx index 14cf3cb5..8710ef47 100644 --- a/apps/frontend/src/app/shared/logo/logo.tsx +++ b/apps/frontend/src/app/shared/logo/logo.tsx @@ -1,19 +1,27 @@ import { ColorScheme } from '@mantine/core'; import React from 'react'; import LargeLogo from './logo-lg.svg'; +import SmallLogo from './logo-sm.svg'; import './logo.scss'; -enum LogoSize { - large = 'lg', -} - export interface LogoProps { colorScheme?: ColorScheme; - size?: LogoSize; + size?: 'sm' | 'lg'; + className?: string; } -export function Logo(props: LogoProps) { - return ; -} +export const Logo = (props: LogoProps) => { + return ( + + ); +}; + +Logo.defaultProps = { + size: 'lg', +}; export default Logo; diff --git a/apps/frontend/src/app/shared/user-avatar/index.ts b/apps/frontend/src/app/shared/user-avatar/index.ts new file mode 100644 index 00000000..0b55e245 --- /dev/null +++ b/apps/frontend/src/app/shared/user-avatar/index.ts @@ -0,0 +1 @@ +export * from './user-avatar'; diff --git a/apps/frontend/src/app/shared/user-avatar/user-avatar.spec.tsx b/apps/frontend/src/app/shared/user-avatar/user-avatar.spec.tsx new file mode 100644 index 00000000..78a2a6e9 --- /dev/null +++ b/apps/frontend/src/app/shared/user-avatar/user-avatar.spec.tsx @@ -0,0 +1,14 @@ +import { render } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import UserAvatar from './user-avatar'; + +describe('User Avatar', () => { + it('should render successfully', () => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/frontend/src/app/shared/user-avatar/user-avatar.tsx b/apps/frontend/src/app/shared/user-avatar/user-avatar.tsx new file mode 100644 index 00000000..c3a2bcee --- /dev/null +++ b/apps/frontend/src/app/shared/user-avatar/user-avatar.tsx @@ -0,0 +1,70 @@ +import { useAuth0 } from '@auth0/auth0-react'; +import { Avatar, Center, Popover, Text } from '@mantine/core'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Button } from '../button'; +import { useNetwork } from '../hooks'; + +export const UserAvatar = () => { + const { user, isLoading, logout } = useAuth0(); + const { online, isLoading: onlineStatusLoading } = useNetwork(); + const userEmail = sessionStorage.getItem('userEmail'); + const navigate = useNavigate(); + + const handleClickLogOut = () => { + if (!online) { + sessionStorage.removeItem('userEmail'); + navigate('/', { replace: true }); + } else { + logout({ returnTo: window.location.origin }); + } + }; + + if (isLoading || onlineStatusLoading) { + return null; + } else { + return ( + + + + {user?.given_name?.charAt(0)} + {user?.family_name?.charAt(0)} + + + + + Logged in as + {!online + ? ' ' + userEmail + : ' ' + user?.given_name + ' ' + user?.family_name} + +
+ +
+
+
+ ); + } +}; + +export default UserAvatar; diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index 26834d24..71dad457 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -4,6 +4,7 @@ import * as ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import MantineTheme from './mantine.config'; import Routes from 'app/routes'; +import { Auth0Provider } from '@login'; // if ('serviceWorker' in navigator) { // window.addEventListener('load', () => { @@ -24,7 +25,9 @@ root.render( theme={MantineTheme} > - + + + diff --git a/apps/frontend/tsconfig.app.json b/apps/frontend/tsconfig.app.json index c932bdf5..24bc138c 100644 --- a/apps/frontend/tsconfig.app.json +++ b/apps/frontend/tsconfig.app.json @@ -6,7 +6,8 @@ "baseUrl": "./src", "paths": { "@shared": ["app/shared/index.ts"], - "@patients": ["app/patients/index.ts"] + "@patients": ["app/patients/index.ts"], + "@login": ["app/login/index.ts"] } }, "files": [ @@ -24,11 +25,5 @@ "**/*.spec.jsx", "**/*.test.jsx" ], - "include": [ - "**/*.js", - "**/*.jsx", - "**/*.ts", - "**/*.tsx", - "src/app/patients/patient-details-page" - ] + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/package-lock.json b/package-lock.json index 694f2060..dc74bf84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "license": "UNLICENSED", "dependencies": { + "@auth0/auth0-react": "^1.10.2", "@emotion/react": "^11.10.0", "@mantine/core": "^5.1.4", "@mantine/dates": "^5.2.0", @@ -373,6 +374,32 @@ "xss": "^1.0.8" } }, + "node_modules/@auth0/auth0-react": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.10.2.tgz", + "integrity": "sha512-s622ac/gLZ9pUX7d5dOhRggF4VI3MaLLCPVBFR5CKOGcPtPG80/4w2jMoBQtRRn6uKS6IVMWAUAFKWB7yATAoQ==", + "dependencies": { + "@auth0/auth0-spa-js": "^1.22.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17 || ^18", + "react-dom": "^16.11.0 || ^17 || ^18" + } + }, + "node_modules/@auth0/auth0-spa-js": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.4.tgz", + "integrity": "sha512-iOboSV+aUsExV1onKvGKEqi626sjJt+61c3EvA4mkn9RM7RV9RMjPI+cInNFHWjwAd2Sdi3LqBj6/MfcHh69dg==", + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.15", + "core-js": "^3.24.0", + "es-cookie": "~1.3.2", + "fast-text-encoding": "^1.0.4", + "promise-polyfill": "^8.2.3", + "unfetch": "^4.2.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -5976,6 +6003,11 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7180,6 +7212,15 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "node_modules/browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "hasInstallScript": true, + "dependencies": { + "lodash": ">=4.17.21" + } + }, "node_modules/browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -9166,6 +9207,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -10155,6 +10201,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -15158,6 +15209,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==" + }, "node_modules/promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -17898,6 +17954,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -19471,6 +19532,28 @@ "xss": "^1.0.8" } }, + "@auth0/auth0-react": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.10.2.tgz", + "integrity": "sha512-s622ac/gLZ9pUX7d5dOhRggF4VI3MaLLCPVBFR5CKOGcPtPG80/4w2jMoBQtRRn6uKS6IVMWAUAFKWB7yATAoQ==", + "requires": { + "@auth0/auth0-spa-js": "^1.22.0" + } + }, + "@auth0/auth0-spa-js": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.4.tgz", + "integrity": "sha512-iOboSV+aUsExV1onKvGKEqi626sjJt+61c3EvA4mkn9RM7RV9RMjPI+cInNFHWjwAd2Sdi3LqBj6/MfcHh69dg==", + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.15", + "core-js": "^3.24.0", + "es-cookie": "~1.3.2", + "fast-text-encoding": "^1.0.4", + "promise-polyfill": "^8.2.3", + "unfetch": "^4.2.0" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -23625,6 +23708,11 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -24530,6 +24618,14 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "requires": { + "lodash": ">=4.17.21" + } + }, "browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -25987,6 +26083,11 @@ "unbox-primitive": "^1.0.2" } }, + "es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -26739,6 +26840,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -30360,6 +30466,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==" + }, "promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -32346,6 +32457,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index af8f2798..cd5a9c42 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "typescript": "~4.7.2" }, "dependencies": { + "@auth0/auth0-react": "^1.10.2", "@emotion/react": "^11.10.0", "@mantine/core": "^5.1.4", "@mantine/dates": "^5.2.0",