diff --git a/eslint.config.js b/eslint.config.js index 931f4d3..a57dbc9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -101,6 +101,9 @@ module.exports = [ react: require('eslint-plugin-react'), 'react-hooks': require('eslint-plugin-react-hooks'), }, + rules: { + 'react/react-in-jsx-scope': 'off', + }, }, //backend and components package rule override diff --git a/package.json b/package.json index 7ce9787..aac712c 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "resolutions": { "**/@types/node": "^22.9.0", - "@types/react": "^17.0.4", + "@types/react": "^19.1.1", "body-parser": "2.2.0", "minimatch": "^3.0.8" }, diff --git a/packages/alexa/tsconfig.json b/packages/alexa/tsconfig.json index afb2cd5..573b410 100644 --- a/packages/alexa/tsconfig.json +++ b/packages/alexa/tsconfig.json @@ -2,13 +2,14 @@ "extends": "../../tsconfig.settings.json", "compilerOptions": { "keyofStringsOnly": false, - "baseUrl": ".", + "baseUrl": "./", "outDir": "lib", "rootDir": "src", "strictNullChecks": true, "experimentalDecorators": true, "esModuleInterop": true, - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "resolveJsonModule": true, "sourceMap": false, "inlineSources": false diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 9085a65..39e6862 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { - "jsx": "react", + "jsx": "react-jsx", "outDir": "lib", "rootDir": "src", "strict": true, @@ -9,8 +9,8 @@ "target": "ESNext", "lib": ["ESNEXT", "DOM"], "esModuleInterop": true, - "moduleResolution": "Node", - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "keyofStringsOnly": false }, "include": ["src/**/*"] diff --git a/packages/authorizer/tsconfig.json b/packages/authorizer/tsconfig.json index abccbaf..87b3141 100644 --- a/packages/authorizer/tsconfig.json +++ b/packages/authorizer/tsconfig.json @@ -8,11 +8,12 @@ "strictNullChecks": true, "experimentalDecorators": true, "esModuleInterop": true, - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "resolveJsonModule": true, "sourceMap": false, "inlineSources": false, - "jsx": "react" + "jsx": "react-jsx" }, "include": ["src/**/*"] } diff --git a/packages/backend/package.json b/packages/backend/package.json index a584bd0..ce509bc 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -14,7 +14,6 @@ "dependencies": { "@graphql-tools/schema": "^10.0.25", "@lara/api": "^1.0.0", - "@types/jest": "^30.0.0", "apollo-server-lambda": "^3.13.0", "aws-sdk": "^2.950.0", "casual": "^1.6.2", @@ -34,11 +33,11 @@ "devDependencies": { "@types/aws-lambda": "^8.10.76", "@types/i18n": "^0.13.0", - "@types/jest": "^29.1.2", + "@types/jest": "^30.0.0", "@types/jsonwebtoken": "^9.0.3", "@types/object-hash": "^3.0.6", "@types/uuid": "^8.3.0", "jest": "^30.0.5", - "ts-jest": "^29.0.3" + "ts-jest": "^29.4.1" } } diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 1fffb5f..c26f3c9 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -8,11 +8,12 @@ "strictNullChecks": true, "experimentalDecorators": true, "esModuleInterop": true, - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "resolveJsonModule": true, "sourceMap": false, "inlineSources": false, - "jsx": "react" + "jsx": "react-jsx" }, "references": [{ "path": "../api" }], "include": ["src/**/*"], diff --git a/packages/cleanup/tsconfig.json b/packages/cleanup/tsconfig.json index 6e6cefd..de83539 100644 --- a/packages/cleanup/tsconfig.json +++ b/packages/cleanup/tsconfig.json @@ -8,7 +8,8 @@ "strictNullChecks": true, "experimentalDecorators": true, "esModuleInterop": true, - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "resolveJsonModule": true, "sourceMap": false, "inlineSources": false diff --git a/packages/components/package.json b/packages/components/package.json index 49423ff..83b416c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -11,14 +11,13 @@ }, "dependencies": { "@rebass/grid": "^6.1.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-router-dom": "^5.2.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", "styled-components": "^6.1.19", "styled-modern-normalize": "^0.2.0" }, "devDependencies": { - "@types/react": "^17.0.9", + "@types/react": "^19.1.10", "@types/rebass__grid": "^6.0.6", "@types/styled-components": "^5.1.3" } diff --git a/packages/components/src/admin-overview.tsx b/packages/components/src/admin-overview.tsx index e2cd51b..18d9bc7 100644 --- a/packages/components/src/admin-overview.tsx +++ b/packages/components/src/admin-overview.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { Spacings } from './spacing' @@ -10,6 +10,7 @@ const AdminOverviewGrid = styled.div` type AdminOverviewLayoutProps = { heading: JSX.Element + children: ReactNode } export const AdminOverviewLayout: React.FC = ({ heading, children }) => { @@ -29,6 +30,7 @@ const AdminCreateUserGrid = styled.div` type AdminCreateUserLayoutProps = { headline: JSX.Element description: JSX.Element + children: ReactNode } export const AdminCreateUserLayout: React.FC = ({ headline, description, children }) => { diff --git a/packages/components/src/alexa-settings.tsx b/packages/components/src/alexa-settings.tsx index 311ee35..036b476 100644 --- a/packages/components/src/alexa-settings.tsx +++ b/packages/components/src/alexa-settings.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { StyledIcon } from './icons' diff --git a/packages/components/src/application-settings-layout.tsx b/packages/components/src/application-settings-layout.tsx index 71554f5..1ff735b 100644 --- a/packages/components/src/application-settings-layout.tsx +++ b/packages/components/src/application-settings-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { FontSizes } from './font-size' diff --git a/packages/components/src/archive.tsx b/packages/components/src/archive.tsx index 732a51e..fe7efbe 100644 --- a/packages/components/src/archive.tsx +++ b/packages/components/src/archive.tsx @@ -1,4 +1,4 @@ -import { Link } from 'react-router-dom' +import { Link } from 'react-router' import styled from 'styled-components' import { Container } from './container' diff --git a/packages/components/src/avatar-layout.tsx b/packages/components/src/avatar-layout.tsx index c8984e6..365063f 100644 --- a/packages/components/src/avatar-layout.tsx +++ b/packages/components/src/avatar-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { BorderRadii } from './border-radius' @@ -32,6 +32,7 @@ const StyledLoadingIconWrapper = styled.div` interface AvatarLayoutProps { size: number + children: ReactNode loadingIcon: JSX.Element noImageIcon: JSX.Element } diff --git a/packages/components/src/button-layout.tsx b/packages/components/src/button-layout.tsx index 3d2e84c..ea7371c 100644 --- a/packages/components/src/button-layout.tsx +++ b/packages/components/src/button-layout.tsx @@ -1,4 +1,4 @@ -import React, { ButtonHTMLAttributes } from 'react' +import React, { JSX, ButtonHTMLAttributes } from 'react' import styled, { css } from 'styled-components' import { BorderRadii } from './border-radius' import { FontSizes } from './font-size' diff --git a/packages/components/src/comment-bubble-layout.tsx b/packages/components/src/comment-bubble-layout.tsx index 94b89a7..4a4fe98 100644 --- a/packages/components/src/comment-bubble-layout.tsx +++ b/packages/components/src/comment-bubble-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { BorderRadii } from './border-radius' diff --git a/packages/components/src/day-input-layout.tsx b/packages/components/src/day-input-layout.tsx index 26bc4d1..426b749 100644 --- a/packages/components/src/day-input-layout.tsx +++ b/packages/components/src/day-input-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { FontSizes } from './font-size' import { Spacings } from './spacing' @@ -47,6 +47,7 @@ interface DayInputLayoutInterface { statusLabel: JSX.Element total?: JSX.Element commentsection?: JSX.Element + children: ReactNode } export const DayInputLayout: React.FC = ({ diff --git a/packages/components/src/dropdown-layout.tsx b/packages/components/src/dropdown-layout.tsx index 240c864..c9ca7e3 100644 --- a/packages/components/src/dropdown-layout.tsx +++ b/packages/components/src/dropdown-layout.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Link } from 'react-router-dom' +import { Link } from 'react-router' import styled, { css } from 'styled-components' import { BorderRadii } from './border-radius' diff --git a/packages/components/src/edit-user-content.tsx b/packages/components/src/edit-user-content.tsx index 8959d4f..93dad9e 100644 --- a/packages/components/src/edit-user-content.tsx +++ b/packages/components/src/edit-user-content.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { Spacings } from './spacing' diff --git a/packages/components/src/edit-user.tsx b/packages/components/src/edit-user.tsx index 1fafe71..209636b 100644 --- a/packages/components/src/edit-user.tsx +++ b/packages/components/src/edit-user.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { Container } from './container' diff --git a/packages/components/src/entry-input-layout.tsx b/packages/components/src/entry-input-layout.tsx index 0265e15..e4b9564 100644 --- a/packages/components/src/entry-input-layout.tsx +++ b/packages/components/src/entry-input-layout.tsx @@ -1,4 +1,4 @@ -import React, { HTMLAttributes } from 'react' +import React, { JSX, HTMLAttributes } from 'react' import styled from 'styled-components' import { Spacings } from './spacing' diff --git a/packages/components/src/faq.tsx b/packages/components/src/faq.tsx index a615388..892f941 100644 --- a/packages/components/src/faq.tsx +++ b/packages/components/src/faq.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { BorderRadii } from './border-radius' import { FontSizes } from './font-size' @@ -28,6 +28,7 @@ const StyledFAQHeader = styled.div` type FaqLayoutProps = { title: JSX.Element search: JSX.Element + children: ReactNode } export const FaqLayout: React.FC = ({ title, search, children }) => { diff --git a/packages/components/src/logo.tsx b/packages/components/src/logo.tsx index e594795..8d4f789 100644 --- a/packages/components/src/logo.tsx +++ b/packages/components/src/logo.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react' +import React, { JSX, useContext } from 'react' import { ThemeContext } from 'styled-components' export interface LogoProps { diff --git a/packages/components/src/modal-layout.tsx b/packages/components/src/modal-layout.tsx index 7c7dfcf..d3a1e5b 100644 --- a/packages/components/src/modal-layout.tsx +++ b/packages/components/src/modal-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { Container } from './container' @@ -16,6 +16,7 @@ export interface ModelStylingProps extends ModalOverlayProps, ModalContainerProp interface ModalLayoutProps extends ModelStylingProps { button: JSX.Element + children: ReactNode } const ModalOverlay = styled.div` diff --git a/packages/components/src/navigation-button-link.tsx b/packages/components/src/navigation-button-link.tsx index 89152f9..f4e6e79 100644 --- a/packages/components/src/navigation-button-link.tsx +++ b/packages/components/src/navigation-button-link.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import { Link, LinkProps } from 'react-router-dom' +import React, { JSX } from 'react' +import { Link, LinkProps } from 'react-router' import styled from 'styled-components' import { BorderRadii } from './border-radius' diff --git a/packages/components/src/navigation.tsx b/packages/components/src/navigation.tsx index 0afdac3..35c05dd 100644 --- a/packages/components/src/navigation.tsx +++ b/packages/components/src/navigation.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import { Link, NavLink, NavLinkProps } from 'react-router-dom' +import { Link, NavLink, NavLinkProps } from 'react-router' import styled, { css } from 'styled-components' import { Flex } from '@rebass/grid' diff --git a/packages/components/src/support.tsx b/packages/components/src/support.tsx index 46feeee..a9e6472 100644 --- a/packages/components/src/support.tsx +++ b/packages/components/src/support.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import { Link } from 'react-router-dom' +import React, { JSX, ReactNode } from 'react' +import { Link } from 'react-router' import styled from 'styled-components' import { Container } from './container' @@ -71,6 +71,7 @@ export const StyledSupportTile: React.FC = ({ title, bod type StyledSupportLayoutProps = { illustration: JSX.Element + children: ReactNode } export const StyledSupportLayout: React.FC = ({ illustration, children }) => { diff --git a/packages/components/src/text-time-input.tsx b/packages/components/src/text-time-input.tsx index b0563c8..58728ed 100644 --- a/packages/components/src/text-time-input.tsx +++ b/packages/components/src/text-time-input.tsx @@ -1,5 +1,4 @@ import { FlexProps } from '@rebass/grid' -import React from 'react' import styled, { css } from 'styled-components' import { StyledEntryValueWrapper } from './entry-input-layout' import { FontSizes } from './font-size' diff --git a/packages/components/src/trainee-overview-layout.tsx b/packages/components/src/trainee-overview-layout.tsx index 85d04b9..2a4e55a 100644 --- a/packages/components/src/trainee-overview-layout.tsx +++ b/packages/components/src/trainee-overview-layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX, ReactNode } from 'react' import styled from 'styled-components' import { Flex } from '@rebass/grid' @@ -40,6 +40,7 @@ interface TraineeOverviewLayoutProps { readonly name: string readonly icon: JSX.Element readonly caption: string + readonly children: ReactNode } export const TraineeOverviewLayout: React.FC = ({ diff --git a/packages/components/src/trainee-row.ts b/packages/components/src/trainee-row.ts index ea72636..a6f7b1b 100644 --- a/packages/components/src/trainee-row.ts +++ b/packages/components/src/trainee-row.ts @@ -1,5 +1,5 @@ import styled from 'styled-components' -import { NavLink } from 'react-router-dom' +import { NavLink } from 'react-router' import { FontSizes } from './font-size' import { Spacings } from './spacing' import { StyledIcon } from './icons' diff --git a/packages/components/src/unstyled-link.tsx b/packages/components/src/unstyled-link.tsx index 06cb55c..b14d78e 100644 --- a/packages/components/src/unstyled-link.tsx +++ b/packages/components/src/unstyled-link.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import { Link, LinkProps } from 'react-router-dom' +import { Link, LinkProps } from 'react-router' import styled from 'styled-components' interface UnstyledLinkProps extends LinkProps { diff --git a/packages/components/src/user-form.tsx b/packages/components/src/user-form.tsx index 9086fc0..3108ffd 100644 --- a/packages/components/src/user-form.tsx +++ b/packages/components/src/user-form.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { Spacings } from './spacing' diff --git a/packages/components/src/user-info.tsx b/packages/components/src/user-info.tsx index 42245f6..f692105 100644 --- a/packages/components/src/user-info.tsx +++ b/packages/components/src/user-info.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import styled from 'styled-components' import { FontSizes } from './font-size' diff --git a/packages/components/src/week-overview.ts b/packages/components/src/week-overview.ts index d5aff60..7a6bd5c 100644 --- a/packages/components/src/week-overview.ts +++ b/packages/components/src/week-overview.ts @@ -1,4 +1,4 @@ -import { Link } from 'react-router-dom' +import { Link } from 'react-router' import styled from 'styled-components' import { FontSizes } from './font-size' diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index fd2c1d1..4dc369b 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -6,7 +6,8 @@ "outDir": "lib", "rootDir": "src", "strictNullChecks": true, - "module": "CommonJS" + "moduleResolution": "bundler", + "module": "es2020" }, "include": ["src/**/*"] } diff --git a/packages/email/tsconfig.json b/packages/email/tsconfig.json index e5164e1..070ab87 100644 --- a/packages/email/tsconfig.json +++ b/packages/email/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.settings.json", "compilerOptions": { - "jsx": "react", + "jsx": "react-jsx", "outDir": "lib", "rootDir": "src", "strict": true, @@ -9,8 +9,8 @@ "target": "ESNext", "lib": ["ESNEXT", "DOM"], "esModuleInterop": true, - "moduleResolution": "Node", - "module": "CommonJS", + "moduleResolution": "node", + "module": "commonjs", "keyofStringsOnly": false }, "references": [{ "path": "../api" }], diff --git a/packages/frontend/jest.config.js b/packages/frontend/jest.config.js index 9e2ab75..bd673b5 100644 --- a/packages/frontend/jest.config.js +++ b/packages/frontend/jest.config.js @@ -1,7 +1,7 @@ module.exports = { roots: ['/src/test'], transform: { - '^.+\\.tsx?$': 'ts-jest', + '^.+\\.[tj]sx?$': ['ts-jest', { useESM: true }], }, testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], @@ -10,4 +10,6 @@ module.exports = { supportMail: 'lara@exampleCompany.com', }, }, + transformIgnorePatterns: ['/node_modules/(?!(.*\\.m?js$))'], + extensionsToTreatAsEsm: ['.ts', '.tsx'], } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7e88987..8154e5c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -15,8 +15,8 @@ }, "dependencies": { "@apollo/client": "^3.2.2", - "@azure/msal-browser": "^2.37.1", - "@azure/msal-react": "^1.5.8", + "@azure/msal-browser": "^4.19.0", + "@azure/msal-react": "^3.0.17", "@lara/components": "^1.0.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@rebass/grid": "^6.1.0", @@ -24,17 +24,16 @@ "apollo-link-token-refresh": "0.7.0", "date-fns": "4.1.0", "file-saver": "^2.0.2", - "framer-motion": "^1.11.1", + "framer-motion": "^12.23.12", "graphql": "^16.11.0", "imagetracerjs": "^1.2.6", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-focus-lock": "^2.9.0", - "react-hook-form": "^7.28.1", - "react-localization": "^1.0.15", - "react-router": "^5.2.0", - "react-router-dom": "^5.2.0", - "react-webcam": "^5.2.4", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-focus-lock": "^2.13.6", + "react-hook-form": "^7.62.0", + "react-localization": "^2.0.6", + "react-router": "^7.8.0", + "react-webcam": "^7.2.0", "styled-components": "^6.1.19", "typescript-plugin-styled-components": "3.0.0" }, @@ -47,11 +46,9 @@ "@types/graphql": "^14.5.0", "@types/jest": "^29.1.2", "@types/node": "^22.9.0", - "@types/react": "^17.0.9", - "@types/react-dom": "^17.0.3", - "@types/react-router": "^5.1.13", - "@types/react-router-dom": "^5.1.7", - "@types/react-webcam": "^0.3.1", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@types/react-webcam": "^3.0.0", "@types/rebass__grid": "^6.0.6", "@types/webpack-env": "^1.18.8", "file-loader": "^6.2.0", diff --git a/packages/frontend/src/apollo-provider.tsx b/packages/frontend/src/apollo-provider.tsx index 7de4c3a..09e5950 100644 --- a/packages/frontend/src/apollo-provider.tsx +++ b/packages/frontend/src/apollo-provider.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { ReactNode } from 'react' import { ApolloClient, @@ -13,7 +13,11 @@ import { useTokenRefreshLink } from './hooks/use-token-refresh-link' export const BackendUrl = `${ENVIRONMENT.backendUrl}/backend` -const ApolloProvider: React.FunctionComponent = ({ children }) => { +interface ApolloProviderProps { + children: ReactNode +} + +const ApolloProvider: React.FunctionComponent = ({ children }) => { const authenticationLink = useAuthenticationLink() const refreshTokenLink = useTokenRefreshLink() diff --git a/packages/frontend/src/app-history.ts b/packages/frontend/src/app-history.ts deleted file mode 100644 index 6e1e8db..0000000 --- a/packages/frontend/src/app-history.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createBrowserHistory, History } from 'history' - -class AppHistory { - public static getInstance(): History { - if (!AppHistory.history) { - AppHistory.history = createBrowserHistory() - } - return AppHistory.history - } - - private static history: History -} - -export default AppHistory diff --git a/packages/frontend/src/app.tsx b/packages/frontend/src/app.tsx index 8d60424..0f7fbf1 100644 --- a/packages/frontend/src/app.tsx +++ b/packages/frontend/src/app.tsx @@ -1,9 +1,8 @@ import React from 'react' -import { Router, Switch } from 'react-router-dom' +import { BrowserRouter } from 'react-router' import ApolloProvider from './apollo-provider' -import AppHistory from './app-history' import StatusBar from './components/status-bar' -import Routes from './routes' +import AppRoutes from './routes' import ThemeProvider from './theme-provider' import { AuthenticatedState, AuthenticationContext } from './hooks/use-authentication' import { GlobalFonts } from './components/fonts' @@ -46,13 +45,10 @@ const InnerApp: React.FunctionComponent = () => { {!loading && ( - - - - - + + {ENVIRONMENT.debug && } - + )} diff --git a/packages/frontend/src/components/accordion.tsx b/packages/frontend/src/components/accordion.tsx index 24002dc..76b63a3 100644 --- a/packages/frontend/src/components/accordion.tsx +++ b/packages/frontend/src/components/accordion.tsx @@ -12,7 +12,7 @@ import { interface AccordionProps { title: string - children?: React.ReactNode + children: React.ReactNode forceActive?: boolean } diff --git a/packages/frontend/src/components/dev-role-dropdown.tsx b/packages/frontend/src/components/dev-role-dropdown.tsx index 8372382..8d165d0 100644 --- a/packages/frontend/src/components/dev-role-dropdown.tsx +++ b/packages/frontend/src/components/dev-role-dropdown.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useRef, useEffect, useCallback } from 'react' import styled from 'styled-components' const DropdownWrapper = styled.div` @@ -79,23 +79,35 @@ const Option = styled.li<{ selected: boolean }>` export function CustomDropdown({ value, onChange, + onOpenChange, }: { value: string onChange: (e: React.ChangeEvent | string) => void + onOpenChange?: (open: boolean) => void }) { const [open, setOpen] = useState(false) const dropdownRef = useRef(null) const options = ['Trainee', 'Trainer', 'Admin'] + const setOpenState = useCallback( + (newOpen: boolean) => { + setOpen(newOpen) + if (onOpenChange) { + onOpenChange(newOpen) + } + }, + [onOpenChange] + ) + useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { - setOpen(false) + setOpenState(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) + }, [setOpenState]) const getYOffset = () => { return (options.indexOf(value) + 1) * 100 @@ -105,7 +117,7 @@ export function CustomDropdown({ - setOpen((prev) => !prev)} open={open}> + setOpenState(!open)} open={open}> {value || 'Select user type'} {open && ( diff --git a/packages/frontend/src/components/edit-user-row.tsx b/packages/frontend/src/components/edit-user-row.tsx index 459748b..df72fa5 100644 --- a/packages/frontend/src/components/edit-user-row.tsx +++ b/packages/frontend/src/components/edit-user-row.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { useHistory } from 'react-router-dom' +import { useNavigate } from 'react-router' import { EditUserRowLayout } from '@lara/components' @@ -16,11 +16,11 @@ interface EditUserRowProps { } export const EditUserRow: React.FunctionComponent = ({ user, baseUrl }) => { - const history = useHistory() + const navigate = useNavigate() const url = `/${baseUrl}/${user.id}` const navigateToEditPage = () => { - history.push(url) + navigate(url) } return ( diff --git a/packages/frontend/src/components/ms-sign-in-button.tsx b/packages/frontend/src/components/ms-sign-in-button.tsx index 66b687e..bcfbcfa 100644 --- a/packages/frontend/src/components/ms-sign-in-button.tsx +++ b/packages/frontend/src/components/ms-sign-in-button.tsx @@ -1,5 +1,4 @@ import React, { ButtonHTMLAttributes, useState } from 'react' -import AppHistory from '../app-history' import { PrimaryButton } from './button' import { useMsal } from '@azure/msal-react' import { loginRequest } from '../hooks/ms-auth' @@ -7,6 +6,7 @@ import { useAuthentication } from '../hooks/use-authentication' import { useLoginPageLoginMutation } from '../graphql' import { useIsAuthenticated } from '@azure/msal-react' import { EventMessage, EventType, PopupEvent } from '@azure/msal-browser' +import { useNavigate } from 'react-router' export interface ButtonProps extends ButtonHTMLAttributes { fullsize?: boolean @@ -17,12 +17,13 @@ export const SignInButton: React.FunctionComponent = () => { const { login } = useAuthentication() const [mutate] = useLoginPageLoginMutation() const isAuthenticated = useIsAuthenticated() + const navigate = useNavigate() const [popupWindow, setPopupWindow] = useState(null) const handleLogin = async () => { if (isAuthenticated) { - AppHistory.getInstance().push('/') + navigate('/') } if (popupWindow) { @@ -44,7 +45,8 @@ export const SignInButton: React.FunctionComponent = () => { .then((response) => { const { data } = response if (!data?.login) { - return AppHistory.getInstance().push('/no-user-found') + navigate('/no-user-found') + return } login(data.login) }) diff --git a/packages/frontend/src/components/navigation.tsx b/packages/frontend/src/components/navigation.tsx index 234073c..e768dde 100644 --- a/packages/frontend/src/components/navigation.tsx +++ b/packages/frontend/src/components/navigation.tsx @@ -56,7 +56,7 @@ const Navigation: React.FunctionComponent = () => { const renderTraineeNav = () => { return ( <> - + {strings.navigation.dashhboard} diff --git a/packages/frontend/src/components/status-bar.tsx b/packages/frontend/src/components/status-bar.tsx index af91367..3f604ff 100644 --- a/packages/frontend/src/components/status-bar.tsx +++ b/packages/frontend/src/components/status-bar.tsx @@ -15,10 +15,14 @@ const StatusBar: React.FunctionComponent = ({ currentUser }) => const [mutateUserType] = useDebugSetUsertypeMutation() const [mutateLogin] = useDebugLoginMutation() - const [visible, setVisible] = useState(false) + const [hoverVisible, setHoverVisible] = useState(false) + const [inputFocused, setInputFocused] = useState(false) + const [dropdownOpen, setDropdownOpen] = useState(false) const [id, setId] = useState('') + const visible = hoverVisible || inputFocused || dropdownOpen + const devLogin = () => { setId('') @@ -43,30 +47,21 @@ const StatusBar: React.FunctionComponent = ({ currentUser }) => } const scrollToBottom = () => { - const nearBottom = Math.abs(window.innerHeight + window.scrollY - document.body.scrollHeight) < 2 - - if (nearBottom) { - window.scrollTo({ top: window.scrollY - 1, behavior: 'instant' }) - } - setTimeout(() => { - requestAnimationFrame(() => { - window.scrollTo({ - top: document.body.scrollHeight, - behavior: 'smooth', - }) + window.scrollTo({ + top: document.body.scrollHeight, + behavior: 'instant', }) }, 100) } const handleMouseEnter = () => { scrollToBottom() - setVisible(true) + setHoverVisible(true) } const handleMouseLeave = () => { - window.scrollTo({ top: window.scrollY - 1, behavior: 'instant' }) - setVisible(false) + setHoverVisible(false) } return ( @@ -79,6 +74,7 @@ const StatusBar: React.FunctionComponent = ({ currentUser }) => onChange={(newType) => selectUsertype({ target: { value: newType } } as React.ChangeEvent) } + onOpenChange={setDropdownOpen} /> )} @@ -88,6 +84,8 @@ const StatusBar: React.FunctionComponent = ({ currentUser }) => type="text" value={id} onChange={(e) => setId(e.target.value)} + onFocus={() => setInputFocused(true)} + onBlur={() => setInputFocused(false)} onKeyDown={(e) => e.key === 'Enter' && devLogin()} style={{ height: '18.5px' }} /> diff --git a/packages/frontend/src/components/suggestions.tsx b/packages/frontend/src/components/suggestions.tsx index da0b8e8..34f1d50 100644 --- a/packages/frontend/src/components/suggestions.tsx +++ b/packages/frontend/src/components/suggestions.tsx @@ -6,7 +6,7 @@ import { useSuggestionsDataQuery } from '../graphql' import { useFocusState } from '../hooks/use-focus-state' interface SuggestionProps { - inputRef: React.RefObject + inputRef: React.RefObject submitSuggestion: (text: string) => void } diff --git a/packages/frontend/src/components/text-input.tsx b/packages/frontend/src/components/text-input.tsx index 1e24ad4..6a29329 100644 --- a/packages/frontend/src/components/text-input.tsx +++ b/packages/frontend/src/components/text-input.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { JSX } from 'react' import { StyledTextInput, StyledTextInputLabel } from '@lara/components' diff --git a/packages/frontend/src/context/entry-order/index.tsx b/packages/frontend/src/context/entry-order/index.tsx index 4d7b03d..5ba0601 100644 --- a/packages/frontend/src/context/entry-order/index.tsx +++ b/packages/frontend/src/context/entry-order/index.tsx @@ -1,4 +1,4 @@ -import React, { createContext, FC, useState } from 'react' +import React, { createContext, ReactNode, useState } from 'react' import { Entry } from '../../graphql' export interface EntryOrderContext { @@ -25,7 +25,11 @@ export const EntryOrderContext = createContext({ clearState: () => undefined, }) -export const EntryOrderProvider: FC = (props) => { +interface EntryOrderProviderProps { + children: ReactNode +} + +export const EntryOrderProvider: React.FC = (props) => { const [entry, setEntry] = useState>() const [dayId, setDayId] = useState() const [targetDayId, setTargetDayId] = useState() diff --git a/packages/frontend/src/context/toast/toast-context-provider.tsx b/packages/frontend/src/context/toast/toast-context-provider.tsx index 9c3d3d3..7c8f5b5 100644 --- a/packages/frontend/src/context/toast/toast-context-provider.tsx +++ b/packages/frontend/src/context/toast/toast-context-provider.tsx @@ -1,8 +1,12 @@ -import React, { useRef } from 'react' +import React, { ReactNode, useRef } from 'react' import { Toast, ToastContext, ToastInput } from './' -export const ToastContextProvider: React.FunctionComponent = ({ children }) => { +interface ToastContextProviderProps { + children: ReactNode +} + +export const ToastContextProvider: React.FunctionComponent = ({ children }) => { const [dangerousToasts, setDangerousToasts] = React.useState([]) const toasts = React.useRef([]) diff --git a/packages/frontend/src/frontend.tsx b/packages/frontend/src/frontend.tsx index 863e254..9f00cd2 100644 --- a/packages/frontend/src/frontend.tsx +++ b/packages/frontend/src/frontend.tsx @@ -1,10 +1,12 @@ -import React from 'react' -import ReactDOM from 'react-dom' - import { App } from './app' +import { createRoot } from 'react-dom/client' -if (window.location.hash === '') { - ReactDOM.render(, document.getElementById('app')) +const rootElement = document.getElementById('app') +if (rootElement) { + const root = createRoot(rootElement) + if (window.location.hash === '') { + root.render() + } } if (module.hot && ENVIRONMENT.debug) { diff --git a/packages/frontend/src/hooks/use-focus-state.tsx b/packages/frontend/src/hooks/use-focus-state.tsx index 3160200..7c4042e 100644 --- a/packages/frontend/src/hooks/use-focus-state.tsx +++ b/packages/frontend/src/hooks/use-focus-state.tsx @@ -1,6 +1,6 @@ import React from 'react' -export const useFocusState = (ref: React.RefObject): boolean => { +export const useFocusState = (ref: React.RefObject): boolean => { const [state, setState] = React.useState(false) const handleFocus = () => setState(true) diff --git a/packages/frontend/src/pages/admin-edit-user-page.tsx b/packages/frontend/src/pages/admin-edit-user-page.tsx index 665e9fd..68028c6 100644 --- a/packages/frontend/src/pages/admin-edit-user-page.tsx +++ b/packages/frontend/src/pages/admin-edit-user-page.tsx @@ -21,7 +21,8 @@ type AdminEditUserPageParams = { export const AdminEditUserPage: React.FunctionComponent = () => { const { id } = useParams() - const { data, loading } = useUserPageQuery({ variables: { id } }) + const vars = { variables: { id: id ?? '' } } + const { data, loading } = useUserPageQuery(vars) const [markForDelete, { loading: deleteLoading }] = useMarkUserForDeleteMutation() const [unmarkDelete, { loading: undeleteLoading }] = useUnmarkUserForDeleteMutation() @@ -37,7 +38,7 @@ export const AdminEditUserPage: React.FunctionComponent = () => { const renderDeleteAction = (deleteAt?: string) => { if (deleteAt) { return ( - unmarkDelete({ variables: { id } })}> + unmarkDelete(vars)}> {strings.unmarkDelete} ) @@ -114,7 +115,7 @@ export const AdminEditUserPage: React.FunctionComponent = () => { { - markForDelete({ variables: { id } }).then(() => { + markForDelete(vars).then(() => { toggleDeletionModal() addToast({ icon: 'PersonAttention', diff --git a/packages/frontend/src/pages/alexa-page.tsx b/packages/frontend/src/pages/alexa-page.tsx index c7dc7db..b405590 100644 --- a/packages/frontend/src/pages/alexa-page.tsx +++ b/packages/frontend/src/pages/alexa-page.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Redirect, useLocation, useHistory } from 'react-router' +import { Navigate, useLocation, useNavigate } from 'react-router' import { H1 } from '@lara/components' @@ -9,16 +9,10 @@ import strings from '../locales/localization' import { Template } from '../templates/template' import { PrimaryButton } from '../components/button' -type AlexaPageParams = { - code?: string - scope?: string - state?: string -} - export const AlexaPage: React.FC = () => { - const history = useHistory() + const navigate = useNavigate() - const { search } = useLocation() + const { search } = useLocation() const params = new URLSearchParams(search) const toast = useToastContext() @@ -29,12 +23,12 @@ export const AlexaPage: React.FC = () => { const [mutate, { loading }] = useLinkAlexaMutation() const handleLinkSuccess = () => { - history.push('/settings') + navigate('/settings') toast.addToast({ text: strings.settings.alexa.linkSuccess, type: 'success' }) } const handleLinkError = () => { - history.push('/') + navigate('/') toast.addToast({ text: strings.settings.alexa.linkError, type: 'error' }) } @@ -55,7 +49,7 @@ export const AlexaPage: React.FC = () => { } if (!code || !state) { - return + return } return ( diff --git a/packages/frontend/src/pages/faq-page.tsx b/packages/frontend/src/pages/faq-page.tsx index 21e765b..bbb589e 100644 --- a/packages/frontend/src/pages/faq-page.tsx +++ b/packages/frontend/src/pages/faq-page.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { JSX, useState } from 'react' import { FaqLayout, diff --git a/packages/frontend/src/pages/missing-page.tsx b/packages/frontend/src/pages/missing-page.tsx index 1f9db83..a46e3da 100644 --- a/packages/frontend/src/pages/missing-page.tsx +++ b/packages/frontend/src/pages/missing-page.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { RouteComponentProps } from 'react-router' +import { useNavigate } from 'react-router' import { H1, H2, Spacer } from '@lara/components' import { Flex } from '@rebass/grid' @@ -8,17 +8,20 @@ import { PrimaryButton } from '../components/button' import strings from '../locales/localization' import { Template } from '../templates/template' -const MissingPage: React.FunctionComponent = ({ history }) => ( - -) +const MissingPage: React.FunctionComponent = () => { + const navigate = useNavigate() + return ( + + ) +} export default MissingPage diff --git a/packages/frontend/src/pages/oauth-page.tsx b/packages/frontend/src/pages/oauth-page.tsx index bd9a0df..a8e5411 100644 --- a/packages/frontend/src/pages/oauth-page.tsx +++ b/packages/frontend/src/pages/oauth-page.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Redirect, useHistory, useLocation } from 'react-router' +import { Navigate, useNavigate, useLocation } from 'react-router' import { H1 } from '@lara/components' @@ -8,14 +8,9 @@ import { useCreateOAuthCodeMutation } from '../graphql' import { Template } from '../templates/template' import { useToastContext } from '../hooks/use-toast-context' -type OAuthParams = { - state?: string - redirectUri?: string -} - export const OAuthPage: React.FC = () => { - const history = useHistory() - const { search } = useLocation() + const navigate = useNavigate() + const { search } = useLocation() const params = new URLSearchParams(search) const toast = useToastContext() @@ -29,7 +24,7 @@ export const OAuthPage: React.FC = () => { mutate().then(({ data }) => { if (!data?.createOAuthCode) { toast.addToast({ text: 'Es konnte keine Autorisierung durchgeführt werden', type: 'error' }) - history.push('/') + navigate('/') return } @@ -38,7 +33,7 @@ export const OAuthPage: React.FC = () => { } if (!state || !redirectUri) { - return + return } return ( diff --git a/packages/frontend/src/pages/report-page.tsx b/packages/frontend/src/pages/report-page.tsx index ad26a87..3cafbab 100644 --- a/packages/frontend/src/pages/report-page.tsx +++ b/packages/frontend/src/pages/report-page.tsx @@ -1,6 +1,6 @@ import { GraphQLError } from 'graphql' import React from 'react' -import { Redirect, RouteComponentProps } from 'react-router' +import { Navigate, useParams, useNavigate } from 'react-router' import { Container, H2, Paragraph, Spacer, StyledTopBorderWrapper } from '@lara/components' import { Box, Flex } from '@rebass/grid' @@ -26,17 +26,15 @@ import strings from '../locales/localization' import { Template } from '../templates/template' import { useReportHelper } from '../helper/report-helper' -interface ReportPageParams { - year: string - week: string -} - -const ReportPage: React.FunctionComponent> = (props) => { +const ReportPage: React.FunctionComponent = () => { + const navigate = useNavigate() const { getFinishedDays } = useReportHelper() + const { year, week } = useParams() + const variables: ReportPageDataQueryVariables = { - year: parseInt(props.match.params.year, 10), - week: parseInt(props.match.params.week, 10), + year: parseInt(year ?? '', 10), + week: parseInt(week ?? '', 10), } const { loading, data } = useReportPageDataQuery({ variables }) @@ -101,18 +99,14 @@ const ReportPage: React.FunctionComponent> const handoverReport = () => { void updateReport({ status: ReportStatus.Review }) - props.history.push('/') + navigate('/') } const unarchiveReport = () => { void updateReport({ status: ReportStatus.Reopened }) - props.history.push('/') + navigate('/') } - const { match } = props - const { params } = match - const { week } = params - const finishedDays = report && getFinishedDays(report) const reportArchived = report?.status === ReportStatus.Archived @@ -181,7 +175,7 @@ const ReportPage: React.FunctionComponent>