Upgrade frontend to React 18 (#3353)

Co-authored-by: Kira Pilot <kira.pilot23@gmail.com>
This commit is contained in:
Ammar Bandukwala
2022-08-22 15:42:06 -05:00
committed by GitHub
parent 6fde537f9c
commit 2ee6acb2ad
121 changed files with 2465 additions and 2293 deletions

View File

@ -39,16 +39,17 @@
"cron-parser": "4.5.0", "cron-parser": "4.5.0",
"cronstrue": "2.11.0", "cronstrue": "2.11.0",
"dayjs": "1.11.4", "dayjs": "1.11.4",
"formik": "2.2.9", "formik": "^2.2.9",
"front-matter": "4.0.2", "front-matter": "4.0.2",
"history": "5.3.0", "history": "5.3.0",
"just-debounce-it": "3.0.1", "just-debounce-it": "3.0.1",
"react": "17.0.2", "react": "^18.2.0",
"react-dom": "17.0.2", "react-dom": "18.2.0",
"react-helmet": "6.1.0", "react-helmet-async": "1.3.0",
"react-markdown": "8.0.3", "react-markdown": "8.0.3",
"react-router-dom": "6.3.0", "react-router-dom": "6.3.0",
"sourcemapped-stacktrace": "1.1.11", "sourcemapped-stacktrace": "1.1.11",
"swr": "1.3.0",
"tzdata": "1.0.30", "tzdata": "1.0.30",
"uuid": "8.3.2", "uuid": "8.3.2",
"xstate": "4.32.1", "xstate": "4.32.1",
@ -70,13 +71,13 @@
"@storybook/addon-links": "6.5.9", "@storybook/addon-links": "6.5.9",
"@storybook/react": "6.4.22", "@storybook/react": "6.4.22",
"@testing-library/jest-dom": "5.16.4", "@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "14.3.0", "@testing-library/user-event": "^14.4.3",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/jest": "27.4.1", "@types/jest": "27.4.1",
"@types/node": "14.18.22", "@types/node": "14.18.22",
"@types/react": "17.0.44", "@types/react": "18.0.15",
"@types/react-dom": "17.0.16", "@types/react-dom": "18.0.6",
"@types/react-helmet": "6.1.5", "@types/react-helmet": "6.1.5",
"@types/superagent": "4.1.15", "@types/superagent": "4.1.15",
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
@ -105,7 +106,7 @@
"jest-runner-eslint": "1.0.0", "jest-runner-eslint": "1.0.0",
"jest-websocket-mock": "2.3.0", "jest-websocket-mock": "2.3.0",
"mini-css-extract-plugin": "2.6.1", "mini-css-extract-plugin": "2.6.1",
"msw": "0.42.0", "msw": "^0.44.2",
"prettier": "2.7.1", "prettier": "2.7.1",
"prettier-plugin-organize-imports": "3.0.0", "prettier-plugin-organize-imports": "3.0.0",
"react-hot-loader": "4.13.0", "react-hot-loader": "4.13.0",

View File

@ -35,7 +35,6 @@ const AuditPage = lazy(() => import("./pages/AuditPage/AuditPage"))
export const AppRouter: FC = () => { export const AppRouter: FC = () => {
const xServices = useContext(XServiceContext) const xServices = useContext(XServiceContext)
const permissions = useSelector(xServices.authXService, selectPermissions) const permissions = useSelector(xServices.authXService, selectPermissions)
return ( return (
<Suspense fallback={<></>}> <Suspense fallback={<></>}>
<Routes> <Routes>

View File

@ -1,5 +1,5 @@
import { inspect } from "@xstate/inspect" import { inspect } from "@xstate/inspect"
import ReactDOM from "react-dom" import { createRoot } from "react-dom/client"
import { Interpreter } from "xstate" import { Interpreter } from "xstate"
import { App } from "./app" import { App } from "./app"
@ -25,7 +25,11 @@ const main = () => {
██████▀▄█ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀ ██████▀▄█ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀
`) `)
const element = document.getElementById("root") const element = document.getElementById("root")
ReactDOM.render(<App />, element) if (element === null) {
throw new Error("root element is null")
}
const root = createRoot(element)
root.render(<App />)
} }
main() main()

View File

@ -1,6 +1,6 @@
import { FC } from "react" import { FC, PropsWithChildren } from "react"
const ReactMarkdown: FC = ({ children }) => { const ReactMarkdown: FC<PropsWithChildren<unknown>> = ({ children }) => {
return <div data-testid="markdown">{children}</div> return <div data-testid="markdown">{children}</div>
} }

View File

@ -1,6 +1,7 @@
import CssBaseline from "@material-ui/core/CssBaseline" import CssBaseline from "@material-ui/core/CssBaseline"
import ThemeProvider from "@material-ui/styles/ThemeProvider" import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { FC } from "react" import { FC } from "react"
import { HelmetProvider } from "react-helmet-async"
import { BrowserRouter as Router } from "react-router-dom" import { BrowserRouter as Router } from "react-router-dom"
import { AppRouter } from "./AppRouter" import { AppRouter } from "./AppRouter"
import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary" import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary"
@ -12,15 +13,17 @@ import { XServiceProvider } from "./xServices/StateContext"
export const App: FC = () => { export const App: FC = () => {
return ( return (
<Router> <Router>
<ThemeProvider theme={dark}> <HelmetProvider>
<CssBaseline /> <ThemeProvider theme={dark}>
<ErrorBoundary> <CssBaseline />
<XServiceProvider> <ErrorBoundary>
<AppRouter /> <XServiceProvider>
<GlobalSnackbar /> <AppRouter />
</XServiceProvider> <GlobalSnackbar />
</ErrorBoundary> </XServiceProvider>
</ThemeProvider> </ErrorBoundary>
</ThemeProvider>
</HelmetProvider>
</Router> </Router>
) )
} }

View File

@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button"
import Link from "@material-ui/core/Link" import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import ComputerIcon from "@material-ui/icons/Computer" import ComputerIcon from "@material-ui/icons/Computer"
import { FC } from "react" import { FC, PropsWithChildren } from "react"
import * as TypesGen from "../../api/typesGenerated" import * as TypesGen from "../../api/typesGenerated"
import { generateRandomString } from "../../util/random" import { generateRandomString } from "../../util/random"
@ -17,7 +17,12 @@ export interface AppLinkProps {
appIcon?: TypesGen.WorkspaceApp["icon"] appIcon?: TypesGen.WorkspaceApp["icon"]
} }
export const AppLink: FC<AppLinkProps> = ({ userName, workspaceName, appName, appIcon }) => { export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
userName,
workspaceName,
appName,
appIcon,
}) => {
const styles = useStyles() const styles = useStyles()
const href = `/@${userName}/${workspaceName}/apps/${appName}` const href = `/@${userName}/${workspaceName}/apps/${appName}`

View File

@ -1,7 +1,7 @@
import Avatar from "@material-ui/core/Avatar" import Avatar from "@material-ui/core/Avatar"
import Link from "@material-ui/core/Link" import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { FC } from "react" import { FC, PropsWithChildren } from "react"
import { Link as RouterLink } from "react-router-dom" import { Link as RouterLink } from "react-router-dom"
import { firstLetter } from "../../util/firstLetter" import { firstLetter } from "../../util/firstLetter"
import { import {
@ -18,7 +18,7 @@ export interface AvatarDataProps {
avatar?: React.ReactNode avatar?: React.ReactNode
} }
export const AvatarData: FC<AvatarDataProps> = ({ export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
title, title,
subtitle, subtitle,
link, link,

View File

@ -1,6 +1,6 @@
import Popover, { PopoverProps } from "@material-ui/core/Popover" import Popover, { PopoverProps } from "@material-ui/core/Popover"
import { fade, makeStyles } from "@material-ui/core/styles" import { fade, makeStyles } from "@material-ui/core/styles"
import { FC } from "react" import { FC, PropsWithChildren } from "react"
type BorderedMenuVariant = "admin-dropdown" | "user-dropdown" type BorderedMenuVariant = "admin-dropdown" | "user-dropdown"
@ -8,7 +8,11 @@ export type BorderedMenuProps = Omit<PopoverProps, "variant"> & {
variant?: BorderedMenuVariant variant?: BorderedMenuVariant
} }
export const BorderedMenu: FC<BorderedMenuProps> = ({ children, variant, ...rest }) => { export const BorderedMenu: FC<PropsWithChildren<BorderedMenuProps>> = ({
children,
variant,
...rest
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -26,7 +26,7 @@ interface BorderedMenuRowProps {
onClick?: () => void onClick?: () => void
} }
export const BorderedMenuRow: FC<BorderedMenuRowProps> = ({ export const BorderedMenuRow: FC<React.PropsWithChildren<BorderedMenuRowProps>> = ({
active, active,
description, description,
Icon, Icon,

View File

@ -30,7 +30,10 @@ export interface BuildsTableProps {
className?: string className?: string
} }
export const BuildsTable: FC<BuildsTableProps> = ({ builds, className }) => { export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
builds,
className,
}) => {
const { username, workspace: workspaceName } = useParams() const { username, workspace: workspaceName } = useParams()
const isLoading = !builds const isLoading = !builds
const theme: Theme = useTheme() const theme: Theme = useTheme()

View File

@ -8,7 +8,7 @@ export interface CliAuthTokenProps {
sessionToken: string sessionToken: string
} }
export const CliAuthToken: FC<CliAuthTokenProps> = ({ sessionToken }) => { export const CliAuthToken: FC<React.PropsWithChildren<CliAuthTokenProps>> = ({ sessionToken }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
<Paper className={styles.container}> <Paper className={styles.container}>

View File

@ -9,7 +9,11 @@ export interface CodeBlockProps {
className?: string className?: string
} }
export const CodeBlock: FC<CodeBlockProps> = ({ lines, ctas, className = "" }) => { export const CodeBlock: FC<React.PropsWithChildren<CodeBlockProps>> = ({
lines,
ctas,
className = "",
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -14,7 +14,7 @@ export interface CodeExampleProps {
/** /**
* Component to show single-line code examples, with a copy button * Component to show single-line code examples, with a copy button
*/ */
export const CodeExample: FC<CodeExampleProps> = ({ export const CodeExample: FC<React.PropsWithChildren<CodeExampleProps>> = ({
code, code,
className, className,
buttonClassName, buttonClassName,

View File

@ -1,11 +1,12 @@
import { fireEvent, render } from "@testing-library/react" import { fireEvent, render } from "@testing-library/react"
import { FC } from "react" import { FC } from "react"
import { act } from "react-dom/test-utils"
import { WrapperComponent } from "../../testHelpers/renderHelpers" import { WrapperComponent } from "../../testHelpers/renderHelpers"
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog" import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"
namespace Helpers { namespace Helpers {
export const Component: FC<ConfirmDialogProps> = (props: ConfirmDialogProps) => { export const Component: FC<React.PropsWithChildren<ConfirmDialogProps>> = (
props: ConfirmDialogProps,
) => {
return ( return (
<WrapperComponent> <WrapperComponent>
<ConfirmDialog {...props} /> <ConfirmDialog {...props} />
@ -116,9 +117,7 @@ describe("ConfirmDialog", () => {
// When // When
const { getByText } = render(<Helpers.Component {...props} />) const { getByText } = render(<Helpers.Component {...props} />)
act(() => { fireEvent.click(getByText("CANCEL"))
fireEvent.click(getByText("CANCEL"))
})
// Then // Then
expect(onCloseMock).toBeCalledTimes(1) expect(onCloseMock).toBeCalledTimes(1)
@ -140,9 +139,7 @@ describe("ConfirmDialog", () => {
// When // When
const { getByText } = render(<Helpers.Component {...props} />) const { getByText } = render(<Helpers.Component {...props} />)
act(() => { fireEvent.click(getByText("CONFIRM"))
fireEvent.click(getByText("CONFIRM"))
})
// Then // Then
expect(onCloseMock).toBeCalledTimes(0) expect(onCloseMock).toBeCalledTimes(0)

View File

@ -78,7 +78,7 @@ const useStyles = makeStyles((theme) => ({
* Quick-use version of the Dialog component with slightly alternative styles, * Quick-use version of the Dialog component with slightly alternative styles,
* great to use for dialogs that don't have any interaction beyond yes / no. * great to use for dialogs that don't have any interaction beyond yes / no.
*/ */
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({ export const ConfirmDialog: React.FC<React.PropsWithChildren<ConfirmDialogProps>> = ({
cancelText, cancelText,
confirmLoading, confirmLoading,
confirmText, confirmText,

View File

@ -22,7 +22,7 @@ export const Language = {
/** /**
* Copy button used inside the CodeBlock component internally * Copy button used inside the CodeBlock component internally
*/ */
export const CopyButton: React.FC<CopyButtonProps> = ({ export const CopyButton: React.FC<React.PropsWithChildren<CopyButtonProps>> = ({
text, text,
ctaCopy, ctaCopy,
wrapperClassName = "", wrapperClassName = "",

View File

@ -35,7 +35,7 @@ const validationSchema = Yup.object({
username: nameValidator(Language.usernameLabel), username: nameValidator(Language.usernameLabel),
}) })
export const CreateUserForm: FC<CreateUserFormProps> = ({ export const CreateUserForm: FC<React.PropsWithChildren<CreateUserFormProps>> = ({
onSubmit, onSubmit,
onCancel, onCancel,
formErrors, formErrors,

View File

@ -12,11 +12,9 @@ export interface DeleteWorkspaceDialogProps {
handleCancel: () => void handleCancel: () => void
} }
export const DeleteWorkspaceDialog: React.FC<DeleteWorkspaceDialogProps> = ({ export const DeleteWorkspaceDialog: React.FC<
isOpen, React.PropsWithChildren<DeleteWorkspaceDialogProps>
handleCancel, > = ({ isOpen, handleCancel, handleConfirm }) => (
handleConfirm,
}) => (
<ConfirmDialog <ConfirmDialog
type="delete" type="delete"
hideCancel={false} hideCancel={false}

View File

@ -23,7 +23,7 @@ export interface EmptyStateProps {
* EmptyState's props extend the [Material UI Box component](https://material-ui.com/components/box/) * EmptyState's props extend the [Material UI Box component](https://material-ui.com/components/box/)
* that you can directly pass props through to to customize the shape and layout of it. * that you can directly pass props through to to customize the shape and layout of it.
*/ */
export const EmptyState: FC<EmptyStateProps> = (props) => { export const EmptyState: FC<React.PropsWithChildren<EmptyStateProps>> = (props) => {
const { message, description, cta, descriptionClassName, className, ...boxProps } = props const { message, description, cta, descriptionClassName, className, ...boxProps } = props
const styles = useStyles() const styles = useStyles()

View File

@ -25,7 +25,7 @@ export interface EnterpriseSnackbarProps extends MuiSnackbarProps {
* *
* See original component's Material UI documentation here: https://material-ui.com/components/snackbars/ * See original component's Material UI documentation here: https://material-ui.com/components/snackbars/
*/ */
export const EnterpriseSnackbar: FC<EnterpriseSnackbarProps> = ({ export const EnterpriseSnackbar: FC<React.PropsWithChildren<EnterpriseSnackbarProps>> = ({
onClose, onClose,
variant = "info", variant = "info",
ContentProps = {}, ContentProps = {},

View File

@ -1,7 +1,7 @@
import { Component, ReactNode } from "react" import React, { Component, ReactNode } from "react"
import { RuntimeErrorState } from "../RuntimeErrorState/RuntimeErrorState" import { RuntimeErrorState } from "../RuntimeErrorState/RuntimeErrorState"
type ErrorBoundaryProps = Record<string, unknown> type ErrorBoundaryProps = React.PropsWithChildren<unknown>
interface ErrorBoundaryState { interface ErrorBoundaryState {
error: Error | null error: Error | null

View File

@ -23,7 +23,7 @@ export interface ErrorSummaryProps {
defaultMessage?: string defaultMessage?: string
} }
export const ErrorSummary: FC<ErrorSummaryProps> = ({ export const ErrorSummary: FC<React.PropsWithChildren<ErrorSummaryProps>> = ({
error, error,
retry, retry,
dismissible, dismissible,

View File

@ -18,7 +18,7 @@ export interface FooterProps {
buildInfo?: TypesGen.BuildInfoResponse buildInfo?: TypesGen.BuildInfoResponse
} }
export const Footer: React.FC<FooterProps> = ({ buildInfo }) => { export const Footer: React.FC<React.PropsWithChildren<FooterProps>> = ({ buildInfo }) => {
const styles = useFooterStyles() const styles = useFooterStyles()
const githubUrl = `https://github.com/coder/coder/issues/new?labels=needs+grooming&body=${encodeURIComponent(`Version: [\`${buildInfo?.version}\`](${buildInfo?.external_url}) const githubUrl = `https://github.com/coder/coder/issues/new?labels=needs+grooming&body=${encodeURIComponent(`Version: [\`${buildInfo?.version}\`](${buildInfo?.external_url})

View File

@ -8,7 +8,9 @@ export interface FormCloseButtonProps {
onClose: () => void onClose: () => void
} }
export const FormCloseButton: React.FC<FormCloseButtonProps> = ({ onClose }) => { export const FormCloseButton: React.FC<React.PropsWithChildren<FormCloseButtonProps>> = ({
onClose,
}) => {
const styles = useStyles() const styles = useStyles()
useEffect(() => { useEffect(() => {

View File

@ -28,7 +28,7 @@ const useStyles = makeStyles((theme) => ({
}, },
})) }))
export const FormFooter: FC<FormFooterProps> = ({ export const FormFooter: FC<React.PropsWithChildren<FormFooterProps>> = ({
onCancel, onCancel,
isLoading, isLoading,
submitLabel = Language.defaultSubmitLabel, submitLabel = Language.defaultSubmitLabel,

View File

@ -39,7 +39,11 @@ export const useStyles = makeStyles((theme) => ({
}, },
})) }))
export const FormSection: FC<FormSectionProps> = ({ title, description, children }) => { export const FormSection: FC<React.PropsWithChildren<FormSectionProps>> = ({
title,
description,
children,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -1,4 +1,4 @@
import { act, fireEvent, render, screen } from "@testing-library/react" import { fireEvent, render, screen } from "@testing-library/react"
import { useFormik } from "formik" import { useFormik } from "formik"
import { FC } from "react" import { FC } from "react"
import * as yup from "yup" import * as yup from "yup"
@ -11,9 +11,9 @@ namespace Helpers {
export const requiredValidationMsg = "required" export const requiredValidationMsg = "required"
export const Component: FC<Omit<FormTextFieldProps<FormValues>, "form" | "formFieldName">> = ( export const Component: FC<
props, React.PropsWithChildren<Omit<FormTextFieldProps<FormValues>, "form" | "formFieldName">>
) => { > = (props) => {
const form = useFormik<FormValues>({ const form = useFormik<FormValues>({
initialValues: { initialValues: {
name: "", name: "",
@ -58,17 +58,13 @@ describe("FormTextField", () => {
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull() expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull()
// When // When
act(() => { fireEvent.focus(el as Element)
fireEvent.focus(el as Element)
})
// Then // Then
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull() expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull()
// When // When
act(() => { fireEvent.blur(el as Element)
fireEvent.blur(el as Element)
})
// Then // Then
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeDefined() expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeDefined()

View File

@ -134,7 +134,7 @@ export const FormTextField = <T,>({
variant={variant} variant={variant}
disabled={disabled || form.isSubmitting} disabled={disabled || form.isSubmitting}
error={isError} error={isError}
helperText={isError ? form.errors[formFieldName] : helperText} helperText={isError ? form.errors[formFieldName]?.toString() : helperText}
id={fieldId} id={fieldId}
InputProps={isPassword ? undefined : InputProps} InputProps={isPassword ? undefined : InputProps}
name={fieldId} name={fieldId}

View File

@ -18,7 +18,7 @@ const useStyles = makeStyles((theme) => ({
}, },
})) }))
export const FormTitle: FC<FormTitleProps> = ({ title, detail }) => { export const FormTitle: FC<React.PropsWithChildren<FormTitleProps>> = ({ title, detail }) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -19,7 +19,12 @@ const useStyles = makeStyles(() => ({
}, },
})) }))
export const FullPageForm: FC<FullPageFormProps> = ({ title, detail, onCancel, children }) => { export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
title,
detail,
onCancel,
children,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (
<main className={styles.root}> <main className={styles.root}>

View File

@ -2,7 +2,7 @@ import Box from "@material-ui/core/Box"
import CircularProgress from "@material-ui/core/CircularProgress" import CircularProgress from "@material-ui/core/CircularProgress"
import { FC } from "react" import { FC } from "react"
export const Loader: FC<{ size?: number }> = ({ size = 26 }) => { export const Loader: FC<React.PropsWithChildren<{ size?: number }>> = ({ size = 26 }) => {
return ( return (
<Box p={4} width="100%" display="flex" alignItems="center" justifyContent="center"> <Box p={4} width="100%" display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={size} /> <CircularProgress size={size} />

View File

@ -17,7 +17,7 @@ export interface LoadingButtonProps extends ButtonProps {
* In Material-UI 5+ - this is built-in, but since we're on an earlier version, * In Material-UI 5+ - this is built-in, but since we're on an earlier version,
* we have to roll our own. * we have to roll our own.
*/ */
export const LoadingButton: FC<LoadingButtonProps> = ({ export const LoadingButton: FC<React.PropsWithChildren<LoadingButtonProps>> = ({
loading = false, loading = false,
loadingLabel, loadingLabel,
children, children,

View File

@ -14,7 +14,7 @@ export interface LogsProps {
className?: string className?: string
} }
export const Logs: FC<LogsProps> = ({ lines, className = "" }) => { export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({ lines, className = "" }) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -24,7 +24,10 @@ interface MarginsProps {
size?: Size size?: Size
} }
export const Margins: FC<MarginsProps> = ({ children, size = "regular" }) => { export const Margins: FC<React.PropsWithChildren<MarginsProps>> = ({
children,
size = "regular",
}) => {
const styles = useStyles({ maxWidth: widthBySize[size] }) const styles = useStyles({ maxWidth: widthBySize[size] })
return <div className={styles.margins}>{children}</div> return <div className={styles.margins}>{children}</div>
} }

View File

@ -25,10 +25,9 @@ export const Language = {
audit: "Audit", audit: "Audit",
} }
const NavItems: React.FC<{ className?: string; canViewAuditLog: boolean }> = ({ const NavItems: React.FC<
className, React.PropsWithChildren<{ className?: string; canViewAuditLog: boolean }>
canViewAuditLog, > = ({ className, canViewAuditLog }) => {
}) => {
const styles = useStyles() const styles = useStyles()
const location = useLocation() const location = useLocation()
@ -63,8 +62,11 @@ const NavItems: React.FC<{ className?: string; canViewAuditLog: boolean }> = ({
</List> </List>
) )
} }
export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
export const NavbarView: React.FC<NavbarViewProps> = ({ user, onSignOut, canViewAuditLog }) => { user,
onSignOut,
canViewAuditLog,
}) => {
const styles = useStyles() const styles = useStyles()
const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(false)

View File

@ -7,7 +7,11 @@ export interface PageHeaderProps {
className?: string className?: string
} }
export const PageHeader: React.FC<PageHeaderProps> = ({ children, actions, className }) => { export const PageHeader: React.FC<React.PropsWithChildren<PageHeaderProps>> = ({
children,
actions,
className,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -22,13 +26,13 @@ export const PageHeader: React.FC<PageHeaderProps> = ({ children, actions, class
) )
} }
export const PageHeaderTitle: React.FC = ({ children }) => { export const PageHeaderTitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return <h1 className={styles.title}>{children}</h1> return <h1 className={styles.title}>{children}</h1>
} }
export const PageHeaderSubtitle: React.FC = ({ children }) => { export const PageHeaderSubtitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return <h2 className={styles.subtitle}>{children}</h2> return <h2 className={styles.subtitle}>{children}</h2>

View File

@ -29,7 +29,11 @@ export interface ParameterInputProps {
onChange: (value: string) => void onChange: (value: string) => void
} }
export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, schema }) => { export const ParameterInput: FC<React.PropsWithChildren<ParameterInputProps>> = ({
disabled,
onChange,
schema,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -42,7 +46,11 @@ export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, sc
) )
} }
const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => { const ParameterField: React.FC<React.PropsWithChildren<ParameterInputProps>> = ({
disabled,
onChange,
schema,
}) => {
if (schema.validation_contains && schema.validation_contains.length > 0) { if (schema.validation_contains && schema.validation_contains.length > 0) {
return ( return (
<RadioGroup <RadioGroup

View File

@ -8,7 +8,10 @@ import React, { useCallback, useState } from "react"
type PasswordFieldProps = Omit<TextFieldProps, "InputProps" | "type"> type PasswordFieldProps = Omit<TextFieldProps, "InputProps" | "type">
export const PasswordField: React.FC<PasswordFieldProps> = ({ variant = "outlined", ...rest }) => { export const PasswordField: React.FC<React.PropsWithChildren<PasswordFieldProps>> = ({
variant = "outlined",
...rest
}) => {
const styles = useStyles() const styles = useStyles()
const [showPassword, setShowPassword] = useState<boolean>(false) const [showPassword, setShowPassword] = useState<boolean>(false)

View File

@ -9,7 +9,7 @@ export interface RequireAuthProps {
children: JSX.Element children: JSX.Element
} }
export const RequireAuth: React.FC<RequireAuthProps> = ({ children }) => { export const RequireAuth: React.FC<React.PropsWithChildren<RequireAuthProps>> = ({ children }) => {
const xServices = useContext(XServiceContext) const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService) const [authState] = useActor(xServices.authXService)
const location = useLocation() const location = useLocation()

View File

@ -24,7 +24,7 @@ export const Language = {
confirmText: "Reset password", confirmText: "Reset password",
} }
export const ResetPasswordDialog: FC<ResetPasswordDialogProps> = ({ export const ResetPasswordDialog: FC<React.PropsWithChildren<ResetPasswordDialogProps>> = ({
open, open,
onClose, onClose,
onConfirm, onConfirm,

View File

@ -33,7 +33,7 @@ interface ResourcesProps {
canUpdateWorkspace: boolean canUpdateWorkspace: boolean
} }
export const Resources: FC<ResourcesProps> = ({ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
resources, resources,
getResourcesError, getResourcesError,
workspace, workspace,

View File

@ -16,7 +16,7 @@ export interface RoleSelectProps {
open?: boolean open?: boolean
} }
export const RoleSelect: FC<RoleSelectProps> = ({ export const RoleSelect: FC<React.PropsWithChildren<RoleSelectProps>> = ({
roles, roles,
selectedRoles, selectedRoles,
loading, loading,

View File

@ -13,7 +13,7 @@ export interface SSHButtonProps {
defaultIsOpen?: boolean defaultIsOpen?: boolean
} }
export const SSHButton: React.FC<SSHButtonProps> = ({ export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
workspaceName, workspaceName,
agentName, agentName,
defaultIsOpen = false, defaultIsOpen = false,

View File

@ -36,7 +36,7 @@ interface FilterFormValues {
export type FilterFormErrors = FormikErrors<FilterFormValues> export type FilterFormErrors = FormikErrors<FilterFormValues>
export const SearchBarWithFilter: React.FC<SearchBarWithFilterProps> = ({ export const SearchBarWithFilter: React.FC<React.PropsWithChildren<SearchBarWithFilterProps>> = ({
filter, filter,
onFilter, onFilter,
presetFilters, presetFilters,

View File

@ -17,7 +17,7 @@ export interface SectionProps {
children?: React.ReactNode children?: React.ReactNode
} }
type SectionFC = FC<SectionProps> & { Action: typeof SectionAction } type SectionFC = FC<React.PropsWithChildren<SectionProps>> & { Action: typeof SectionAction }
export const Section: SectionFC = ({ export const Section: SectionFC = ({
title, title,

View File

@ -11,7 +11,7 @@ const useStyles = makeStyles((theme) => ({
* SectionAction is a content box that call to actions should be placed * SectionAction is a content box that call to actions should be placed
* within * within
*/ */
export const SectionAction: FC = ({ children }) => { export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return <div className={styles.root}>{children}</div> return <div className={styles.root}>{children}</div>
} }

View File

@ -31,7 +31,7 @@ export interface AccountFormProps {
initialTouched?: FormikTouched<AccountFormValues> initialTouched?: FormikTouched<AccountFormValues>
} }
export const AccountForm: FC<AccountFormProps> = ({ export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
email, email,
isLoading, isLoading,
onSubmit, onSubmit,
@ -51,7 +51,7 @@ export const AccountForm: FC<AccountFormProps> = ({
<> <>
<form onSubmit={form.handleSubmit}> <form onSubmit={form.handleSubmit}>
<Stack> <Stack>
{updateProfileError && <ErrorSummary error={updateProfileError} />} {updateProfileError ? <ErrorSummary error={updateProfileError} /> : <></>}
<TextField <TextField
disabled disabled
fullWidth fullWidth

View File

@ -1,6 +1,6 @@
import Box from "@material-ui/core/Box" import Box from "@material-ui/core/Box"
import { FC } from "react" import { FC } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
import { pageTitle } from "../../util/page" import { pageTitle } from "../../util/page"
import { AuthAndFrame } from "../AuthAndFrame/AuthAndFrame" import { AuthAndFrame } from "../AuthAndFrame/AuthAndFrame"

View File

@ -68,7 +68,7 @@ export const SecurityForm: React.FC<SecurityFormProps> = ({
<> <>
<form onSubmit={form.handleSubmit}> <form onSubmit={form.handleSubmit}>
<Stack> <Stack>
{updateSecurityError && <ErrorSummary error={updateSecurityError} />} {updateSecurityError ? <ErrorSummary error={updateSecurityError} /> : <></>}
<TextField <TextField
{...getFieldHelpers("old_password")} {...getFieldHelpers("old_password")}
onChange={onChangeTrimmed(form)} onChange={onChangeTrimmed(form)}

View File

@ -89,7 +89,7 @@ export interface SignInFormProps {
initialTouched?: FormikTouched<BuiltInAuthFormValues> initialTouched?: FormikTouched<BuiltInAuthFormValues>
} }
export const SignInForm: FC<SignInFormProps> = ({ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
authMethods, authMethods,
redirectTo, redirectTo,
isLoading, isLoading,

View File

@ -1,5 +1,5 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { FC } from "react" import { FC, ReactNode } from "react"
import { Footer } from "../../components/Footer/Footer" import { Footer } from "../../components/Footer/Footer"
export const useStyles = makeStyles((theme) => ({ export const useStyles = makeStyles((theme) => ({
@ -21,7 +21,7 @@ export const useStyles = makeStyles((theme) => ({
}, },
})) }))
export const SignInLayout: FC = ({ children }) => { export const SignInLayout: FC<{ children: ReactNode }> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -1,7 +1,6 @@
import Button, { ButtonProps } from "@material-ui/core/Button" import Button, { ButtonProps } from "@material-ui/core/Button"
import ButtonGroup from "@material-ui/core/ButtonGroup" import ButtonGroup from "@material-ui/core/ButtonGroup"
import ClickAwayListener from "@material-ui/core/ClickAwayListener" import ClickAwayListener from "@material-ui/core/ClickAwayListener"
import Grow from "@material-ui/core/Grow"
import MenuItem from "@material-ui/core/MenuItem" import MenuItem from "@material-ui/core/MenuItem"
import MenuList from "@material-ui/core/MenuList" import MenuList from "@material-ui/core/MenuList"
import Paper from "@material-ui/core/Paper" import Paper from "@material-ui/core/Paper"
@ -30,7 +29,7 @@ export interface SplitButtonProps<T> extends Pick<ButtonProps, "color" | "disabl
*/ */
options: SplitButtonOptions<T>[] options: SplitButtonOptions<T>[]
/** /**
* textTransform is applied to the primary button text. Defaults to * textTransform is applied to the primary button text. Defaults PropsWithto
* uppercase * uppercase
*/ */
textTransform?: React.CSSProperties["textTransform"] textTransform?: React.CSSProperties["textTransform"]
@ -104,25 +103,18 @@ export const SplitButton = <T,>({
style={{ zIndex: 1 }} style={{ zIndex: 1 }}
transition transition
> >
{({ TransitionProps, placement }) => ( {() => (
<Grow <Paper>
{...TransitionProps} <ClickAwayListener onClickAway={handleClose}>
style={{ <MenuList id="split-button-menu">
transformOrigin: placement === "bottom" ? "center top" : "center bottom", {options.map((opt, idx) => (
}} <MenuItem key={opt.label} onClick={(e) => handleSelectOpt(e, idx)}>
> {opt.label}
<Paper> </MenuItem>
<ClickAwayListener onClickAway={handleClose}> ))}
<MenuList id="split-button-menu"> </MenuList>
{options.map((opt, idx) => ( </ClickAwayListener>
<MenuItem key={opt.label} onClick={(e) => handleSelectOpt(e, idx)}> </Paper>
{opt.label}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)} )}
</Popper> </Popper>
</> </>

View File

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { CSSProperties } from "@material-ui/core/styles/withStyles" import { CSSProperties } from "@material-ui/core/styles/withStyles"
import { FC } from "react" import { FC } from "react"
import { ReactNode } from "react-markdown/lib/react-markdown"
import { combineClasses } from "../../util/combineClasses" import { combineClasses } from "../../util/combineClasses"
type Direction = "column" | "row" type Direction = "column" | "row"
@ -29,7 +30,7 @@ const useStyles = makeStyles((theme) => ({
}, },
})) }))
export const Stack: FC<StackProps> = ({ export const Stack: FC<StackProps & { children: ReactNode | ReactNode[] }> = ({
children, children,
className, className,
direction = "column", direction = "column",
@ -37,7 +38,12 @@ export const Stack: FC<StackProps> = ({
alignItems, alignItems,
justifyContent, justifyContent,
}) => { }) => {
const styles = useStyles({ spacing, direction, alignItems, justifyContent }) const styles = useStyles({
spacing,
direction,
alignItems,
justifyContent,
})
return <div className={combineClasses([styles.stack, className])}>{children}</div> return <div className={combineClasses([styles.stack, className])}>{children}</div>
} }

View File

@ -10,7 +10,11 @@ export interface TabPanelProps {
menuItems: TabSidebarItem[] menuItems: TabSidebarItem[]
} }
export const TabPanel: FC<TabPanelProps> = ({ children, title, menuItems }) => { export const TabPanel: FC<React.PropsWithChildren<TabPanelProps>> = ({
children,
title,
menuItems,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -16,7 +16,7 @@ export interface TabSidebarProps {
menuItems: TabSidebarItem[] menuItems: TabSidebarItem[]
} }
export const TabSidebar: FC<TabSidebarProps> = ({ menuItems }) => { export const TabSidebar: FC<React.PropsWithChildren<TabSidebarProps>> = ({ menuItems }) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -1,16 +1,16 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import React from "react" import React, { ReactNode } from "react"
import { Stack } from "../Stack/Stack" import { Stack } from "../Stack/Stack"
interface StyleProps { interface StyleProps {
highlight?: boolean highlight?: boolean
} }
export const TableCellData: React.FC = ({ children }) => { export const TableCellData: React.FC<{ children: ReactNode }> = ({ children }) => {
return <Stack spacing={0}>{children}</Stack> return <Stack spacing={0}>{children}</Stack>
} }
export const TableCellDataPrimary: React.FC<{ highlight?: boolean }> = ({ export const TableCellDataPrimary: React.FC<React.PropsWithChildren<{ highlight?: boolean }>> = ({
children, children,
highlight, highlight,
}) => { }) => {
@ -19,7 +19,9 @@ export const TableCellDataPrimary: React.FC<{ highlight?: boolean }> = ({
return <span className={styles.primary}>{children}</span> return <span className={styles.primary}>{children}</span>
} }
export const TableCellDataSecondary: React.FC = ({ children }) => { export const TableCellDataSecondary: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}) => {
const styles = useStyles() const styles = useStyles()
return <span className={styles.secondary}>{children}</span> return <span className={styles.secondary}>{children}</span>

View File

@ -7,9 +7,11 @@ import { combineClasses } from "../../util/combineClasses"
// TableCellLink wraps a TableCell filling the entirety with a Link. // TableCellLink wraps a TableCell filling the entirety with a Link.
// This allows table rows to be clickable with browser-behavior like ctrl+click. // This allows table rows to be clickable with browser-behavior like ctrl+click.
export const TableCellLink: React.FC< export const TableCellLink: React.FC<
TableCellProps & { React.PropsWithChildren<
to: string TableCellProps & {
} to: string
}
>
> = (props) => { > = (props) => {
const styles = useStyles() const styles = useStyles()

View File

@ -1,26 +1,6 @@
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow" import TableRow from "@material-ui/core/TableRow"
import { FC } from "react" import { FC, ReactNode } from "react"
export interface TableHeadersProps { export const TableHeaderRow: FC<{ children: ReactNode }> = ({ children }) => {
columns: string[]
hasMenu?: boolean
}
export const TableHeaderRow: FC = ({ children }) => {
return <TableRow>{children}</TableRow> return <TableRow>{children}</TableRow>
} }
export const TableHeaders: FC<TableHeadersProps> = ({ columns, hasMenu }) => {
return (
<TableHeaderRow>
{columns.map((c, idx) => (
<TableCell key={idx} size="small">
{c}
</TableCell>
))}
{/* 1% is a trick to make the table cell width fit the content */}
{hasMenu && <TableCell width="1%" />}
</TableHeaderRow>
)
}

View File

@ -5,7 +5,7 @@ import { Language as AgentTooltipLanguage } from "../Tooltips/AgentHelpTooltip"
import { Language as ResourceTooltipLanguage } from "../Tooltips/ResourcesHelpTooltip" import { Language as ResourceTooltipLanguage } from "../Tooltips/ResourcesHelpTooltip"
import { TemplateResourcesProps, TemplateResourcesTable } from "./TemplateResourcesTable" import { TemplateResourcesProps, TemplateResourcesTable } from "./TemplateResourcesTable"
const Component: FC<TemplateResourcesProps> = (props) => ( const Component: FC<React.PropsWithChildren<TemplateResourcesProps>> = (props) => (
<WrapperComponent> <WrapperComponent>
<TemplateResourcesTable {...props} /> <TemplateResourcesTable {...props} />
</WrapperComponent> </WrapperComponent>

View File

@ -23,7 +23,9 @@ export interface TemplateResourcesProps {
resources: WorkspaceResource[] resources: WorkspaceResource[]
} }
export const TemplateResourcesTable: FC<TemplateResourcesProps> = ({ resources }) => { export const TemplateResourcesTable: FC<React.PropsWithChildren<TemplateResourcesProps>> = ({
resources,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -26,7 +26,7 @@ export interface TerminalLinkProps {
* If no user name is provided "me" is used however it makes the link not * If no user name is provided "me" is used however it makes the link not
* shareable. * shareable.
*/ */
export const TerminalLink: FC<TerminalLinkProps> = ({ export const TerminalLink: FC<React.PropsWithChildren<TerminalLinkProps>> = ({
agentName, agentName,
userName = "me", userName = "me",
workspaceName, workspaceName,

View File

@ -33,7 +33,11 @@ const useHelpTooltip = () => {
return helpTooltipContext return helpTooltipContext
} }
export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size = "medium" }) => { export const HelpTooltip: React.FC<React.PropsWithChildren<HelpTooltipProps>> = ({
children,
open,
size = "medium",
}) => {
const styles = useStyles({ size }) const styles = useStyles({ size })
const anchorRef = useRef<HTMLButtonElement>(null) const anchorRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(!!open) const [isOpen, setIsOpen] = useState(!!open)
@ -92,19 +96,22 @@ export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size =
) )
} }
export const HelpTooltipTitle: React.FC = ({ children }) => { export const HelpTooltipTitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return <h4 className={styles.title}>{children}</h4> return <h4 className={styles.title}>{children}</h4>
} }
export const HelpTooltipText: React.FC = ({ children }) => { export const HelpTooltipText: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return <p className={styles.text}>{children}</p> return <p className={styles.text}>{children}</p>
} }
export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href }) => { export const HelpTooltipLink: React.FC<React.PropsWithChildren<{ href: string }>> = ({
children,
href,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -115,11 +122,13 @@ export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href })
) )
} }
export const HelpTooltipAction: React.FC<{ export const HelpTooltipAction: React.FC<
icon: Icon React.PropsWithChildren<{
onClick: () => void icon: Icon
ariaLabel?: string onClick: () => void
}> = ({ children, icon: Icon, onClick, ariaLabel }) => { ariaLabel?: string
}>
> = ({ children, icon: Icon, onClick, ariaLabel }) => {
const styles = useStyles() const styles = useStyles()
const tooltip = useHelpTooltip() const tooltip = useHelpTooltip()
@ -139,7 +148,7 @@ export const HelpTooltipAction: React.FC<{
) )
} }
export const HelpTooltipLinksGroup: React.FC = ({ children }) => { export const HelpTooltipLinksGroup: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -19,7 +19,10 @@ interface TooltipProps {
ariaLabel?: string ariaLabel?: string
} }
export const OutdatedHelpTooltip: FC<TooltipProps> = ({ onUpdateVersion, ariaLabel }) => { export const OutdatedHelpTooltip: FC<React.PropsWithChildren<TooltipProps>> = ({
onUpdateVersion,
ariaLabel,
}) => {
return ( return (
<HelpTooltip size="small"> <HelpTooltip size="small">
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle> <HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>

View File

@ -13,7 +13,7 @@ namespace Helpers {
onPrimaryTextSelect: jest.fn(), onPrimaryTextSelect: jest.fn(),
} }
export const Component: FC<UserCellProps> = (props) => ( export const Component: FC<React.PropsWithChildren<UserCellProps>> = (props) => (
<WrapperComponent> <WrapperComponent>
<UserCell {...props} /> <UserCell {...props} />
</WrapperComponent> </WrapperComponent>

View File

@ -35,7 +35,7 @@ const useStyles = makeStyles((theme) => ({
* UserCell is a single cell in an audit log table row that contains user-level * UserCell is a single cell in an audit log table row that contains user-level
* information * information
*/ */
export const UserCell: FC<UserCellProps> = ({ export const UserCell: FC<React.PropsWithChildren<UserCellProps>> = ({
Avatar, Avatar,
caption, caption,
primaryText, primaryText,

View File

@ -1,4 +1,4 @@
import { screen } from "@testing-library/react" import { fireEvent, screen } from "@testing-library/react"
import { MockUser } from "../../testHelpers/entities" import { MockUser } from "../../testHelpers/entities"
import { render } from "../../testHelpers/renderHelpers" import { render } from "../../testHelpers/renderHelpers"
import { Language } from "../UserDropdownContent/UserDropdownContent" import { Language } from "../UserDropdownContent/UserDropdownContent"
@ -7,7 +7,7 @@ import { UserDropdown, UserDropdownProps } from "./UsersDropdown"
const renderAndClick = async (props: Partial<UserDropdownProps> = {}) => { const renderAndClick = async (props: Partial<UserDropdownProps> = {}) => {
render(<UserDropdown user={props.user ?? MockUser} onSignOut={props.onSignOut ?? jest.fn()} />) render(<UserDropdown user={props.user ?? MockUser} onSignOut={props.onSignOut ?? jest.fn()} />)
const trigger = await screen.findByTestId("user-dropdown-trigger") const trigger = await screen.findByTestId("user-dropdown-trigger")
trigger.click() fireEvent.click(trigger)
} }
describe("UserDropdown", () => { describe("UserDropdown", () => {

View File

@ -14,7 +14,7 @@ export interface UserDropdownProps {
onSignOut: () => void onSignOut: () => void
} }
export const UserDropdown: React.FC<UserDropdownProps> = ({ export const UserDropdown: React.FC<React.PropsWithChildren<UserDropdownProps>> = ({
user, user,
onSignOut, onSignOut,
}: UserDropdownProps) => { }: UserDropdownProps) => {

View File

@ -28,7 +28,7 @@ export interface UsersTableProps {
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
} }
export const UsersTable: FC<UsersTableProps> = ({ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
users, users,
roles, roles,
onSuspendUser, onSuspendUser,

View File

@ -30,7 +30,7 @@ interface UsersTableBodyProps {
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
} }
export const UsersTableBody: FC<UsersTableBodyProps> = ({ export const UsersTableBody: FC<React.PropsWithChildren<UsersTableBodyProps>> = ({
users, users,
roles, roles,
onSuspendUser, onSuspendUser,

View File

@ -23,7 +23,7 @@ export interface VersionsTableProps {
versions?: TypesGen.TemplateVersion[] versions?: TypesGen.TemplateVersion[]
} }
export const VersionsTable: FC<VersionsTableProps> = ({ versions }) => { export const VersionsTable: FC<React.PropsWithChildren<VersionsTableProps>> = ({ versions }) => {
const isLoading = !versions const isLoading = !versions
const theme: Theme = useTheme() const theme: Theme = useTheme()

View File

@ -1,6 +1,6 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography" import Typography from "@material-ui/core/Typography"
import { FC } from "react" import { FC, PropsWithChildren } from "react"
import { CoderIcon } from "../Icons/CoderIcon" import { CoderIcon } from "../Icons/CoderIcon"
const Language = { const Language = {
@ -11,7 +11,9 @@ const Language = {
), ),
} }
export const Welcome: FC<{ message?: JSX.Element }> = ({ message = Language.defaultMessage }) => { export const Welcome: FC<PropsWithChildren<{ message?: JSX.Element }>> = ({
message = Language.defaultMessage,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -49,7 +49,7 @@ export interface WorkspaceProps {
/** /**
* Workspace is the top-level component for viewing an individual workspace * Workspace is the top-level component for viewing an individual workspace
*/ */
export const Workspace: FC<WorkspaceProps> = ({ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
bannerProps, bannerProps,
scheduleProps, scheduleProps,
handleStart, handleStart,
@ -69,11 +69,15 @@ export const Workspace: FC<WorkspaceProps> = ({
return ( return (
<Margins> <Margins>
<Stack spacing={1}> <Stack spacing={1}>
{workspaceErrors[WorkspaceErrors.BUILD_ERROR] && ( {workspaceErrors[WorkspaceErrors.BUILD_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible /> <ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
) : (
<></>
)} )}
{workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] && ( {workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible /> <ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible />
) : (
<></>
)} )}
</Stack> </Stack>
<PageHeader <PageHeader

View File

@ -9,7 +9,7 @@ export interface WorkspaceActionButtonProps {
ariaLabel?: string ariaLabel?: string
} }
export const WorkspaceActionButton: FC<WorkspaceActionButtonProps> = ({ export const WorkspaceActionButton: FC<React.PropsWithChildren<WorkspaceActionButtonProps>> = ({
label, label,
icon, icon,
onClick, onClick,

View File

@ -27,7 +27,7 @@ interface WorkspaceAction {
handleAction: () => void handleAction: () => void
} }
export const UpdateButton: FC<WorkspaceAction> = ({ handleAction }) => { export const UpdateButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -37,7 +37,7 @@ export const UpdateButton: FC<WorkspaceAction> = ({ handleAction }) => {
) )
} }
export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => { export const StartButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -50,7 +50,7 @@ export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => {
) )
} }
export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => { export const StopButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -63,7 +63,7 @@ export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => {
) )
} }
export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => { export const DeleteButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -76,7 +76,7 @@ export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => {
) )
} }
export const CancelButton: FC<WorkspaceAction> = ({ handleAction }) => { export const CancelButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles() const styles = useStyles()
// this is an icon button, so it's important to include an aria label // this is an icon button, so it's important to include an aria label
@ -94,7 +94,7 @@ interface DisabledProps {
workspaceState: WorkspaceStateEnum workspaceState: WorkspaceStateEnum
} }
export const DisabledButton: FC<DisabledProps> = ({ workspaceState }) => { export const DisabledButton: FC<React.PropsWithChildren<DisabledProps>> = ({ workspaceState }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
@ -108,7 +108,7 @@ interface LoadingProps {
label: string label: string
} }
export const ActionLoadingButton: FC<LoadingProps> = ({ label }) => { export const ActionLoadingButton: FC<React.PropsWithChildren<LoadingProps>> = ({ label }) => {
const styles = useStyles() const styles = useStyles()
return ( return (
<LoadingButton <LoadingButton

View File

@ -8,7 +8,10 @@ export interface DropdownContentProps {
} }
/* secondary workspace CTAs */ /* secondary workspace CTAs */
export const DropdownContent: FC<DropdownContentProps> = ({ secondaryActions, buttonMapping }) => { export const DropdownContent: FC<React.PropsWithChildren<DropdownContentProps>> = ({
secondaryActions,
buttonMapping,
}) => {
const styles = useStyles() const styles = useStyles()
return ( return (

View File

@ -1,4 +1,4 @@
import { screen } from "@testing-library/react" import { fireEvent, screen } from "@testing-library/react"
import { WorkspaceStateEnum } from "util/workspace" import { WorkspaceStateEnum } from "util/workspace"
import * as Mocks from "../../testHelpers/entities" import * as Mocks from "../../testHelpers/entities"
import { render } from "../../testHelpers/renderHelpers" import { render } from "../../testHelpers/renderHelpers"
@ -30,7 +30,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
/>, />,
) )
const trigger = await screen.findByTestId("workspace-actions-button") const trigger = await screen.findByTestId("workspace-actions-button")
trigger.click() fireEvent.click(trigger)
} }
describe("WorkspaceActions", () => { describe("WorkspaceActions", () => {

View File

@ -1,7 +1,7 @@
import Button from "@material-ui/core/Button" import Button from "@material-ui/core/Button"
import Popover from "@material-ui/core/Popover" import Popover from "@material-ui/core/Popover"
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { FC, useEffect, useMemo, useRef, useState } from "react" import { FC, ReactNode, useEffect, useMemo, useRef, useState } from "react"
import { getWorkspaceStatus, WorkspaceStateEnum, WorkspaceStatus } from "util/workspace" import { getWorkspaceStatus, WorkspaceStateEnum, WorkspaceStatus } from "util/workspace"
import { Workspace } from "../../api/typesGenerated" import { Workspace } from "../../api/typesGenerated"
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows" import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
@ -32,6 +32,7 @@ export interface WorkspaceActionsProps {
handleDelete: () => void handleDelete: () => void
handleUpdate: () => void handleUpdate: () => void
handleCancel: () => void handleCancel: () => void
children?: ReactNode
} }
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({

View File

@ -16,7 +16,7 @@ export interface WorkspaceDeletedBannerProps {
handleClick: () => void handleClick: () => void
} }
export const WorkspaceDeletedBanner: FC<WorkspaceDeletedBannerProps> = ({ export const WorkspaceDeletedBanner: FC<React.PropsWithChildren<WorkspaceDeletedBannerProps>> = ({
workspace, workspace,
handleClick, handleClick,
}) => { }) => {

View File

@ -36,7 +36,7 @@ export interface WorkspaceScheduleProps {
canUpdateWorkspace: boolean canUpdateWorkspace: boolean
} }
export const WorkspaceSchedule: FC<WorkspaceScheduleProps> = ({ export const WorkspaceSchedule: FC<React.PropsWithChildren<WorkspaceScheduleProps>> = ({
workspace, workspace,
canUpdateWorkspace, canUpdateWorkspace,
}) => { }) => {

View File

@ -35,7 +35,7 @@ export const shouldDisplay = (workspace: TypesGen.Workspace): boolean => {
} }
} }
export const WorkspaceScheduleBanner: FC<WorkspaceScheduleBannerProps> = ({ export const WorkspaceScheduleBanner: FC<React.PropsWithChildren<WorkspaceScheduleBannerProps>> = ({
isLoading, isLoading,
onExtend, onExtend,
workspace, workspace,

View File

@ -170,7 +170,7 @@ export const validationSchema = Yup.object({
}), }),
}) })
export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({ export const WorkspaceScheduleForm: FC<React.PropsWithChildren<WorkspaceScheduleFormProps>> = ({
submitScheduleError, submitScheduleError,
initialValues, initialValues,
isLoading, isLoading,
@ -221,7 +221,7 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
<FullPageForm onCancel={onCancel} title={Language.formTitle}> <FullPageForm onCancel={onCancel} title={Language.formTitle}>
<form onSubmit={form.handleSubmit} className={styles.form}> <form onSubmit={form.handleSubmit} className={styles.form}>
<Stack> <Stack>
{submitScheduleError && <ErrorSummary error={submitScheduleError} />} {submitScheduleError ? <ErrorSummary error={submitScheduleError} /> : <></>}
<Section title={Language.startSection}> <Section title={Language.startSection}>
<FormControlLabel <FormControlLabel
control={ control={

View File

@ -14,7 +14,7 @@ export interface WorkspaceSectionProps {
title?: string title?: string
} }
export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({ export const WorkspaceSection: React.FC<React.PropsWithChildren<WorkspaceSectionProps>> = ({
action, action,
children, children,
contentsProps, contentsProps,

View File

@ -124,7 +124,10 @@ export type WorkspaceStatusBadgeProps = {
className?: string className?: string
} }
export const WorkspaceStatusBadge: React.FC<WorkspaceStatusBadgeProps> = ({ build, className }) => { export const WorkspaceStatusBadge: React.FC<React.PropsWithChildren<WorkspaceStatusBadgeProps>> = ({
build,
className,
}) => {
const styles = useStyles() const styles = useStyles()
const theme = useTheme() const theme = useTheme()
const { text, icon, ...colorStyles } = getStatus(theme, build) const { text, icon, ...colorStyles } = getStatus(theme, build)

View File

@ -21,7 +21,9 @@ const Language = {
outdatedLabel: "Outdated", outdatedLabel: "Outdated",
} }
export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ workspaceRef }) => { export const WorkspacesRow: FC<
React.PropsWithChildren<{ workspaceRef: WorkspaceItemMachineRef }>
> = ({ workspaceRef }) => {
const styles = useStyles() const styles = useStyles()
const navigate = useNavigate() const navigate = useNavigate()
const theme: Theme = useTheme() const theme: Theme = useTheme()

View File

@ -22,7 +22,11 @@ export interface WorkspacesTableProps {
filter?: string filter?: string
} }
export const WorkspacesTable: FC<WorkspacesTableProps> = ({ isLoading, workspaceRefs, filter }) => { export const WorkspacesTable: FC<React.PropsWithChildren<WorkspacesTableProps>> = ({
isLoading,
workspaceRefs,
filter,
}) => {
return ( return (
<TableContainer> <TableContainer>
<Table> <Table>

View File

@ -24,7 +24,11 @@ interface TableBodyProps {
filter?: string filter?: string
} }
export const WorkspacesTableBody: FC<TableBodyProps> = ({ isLoading, workspaceRefs, filter }) => { export const WorkspacesTableBody: FC<React.PropsWithChildren<TableBodyProps>> = ({
isLoading,
workspaceRefs,
filter,
}) => {
if (isLoading) { if (isLoading) {
return <TableLoader /> return <TableLoader />
} }

View File

@ -1,5 +1,4 @@
import { waitFor } from "@testing-library/react" import { renderHook, waitFor } from "@testing-library/react"
import { renderHook } from "@testing-library/react-hooks"
import { dispatchCustomEvent } from "../util/events" import { dispatchCustomEvent } from "../util/events"
import { useCustomEvent } from "./events" import { useCustomEvent } from "./events"

View File

@ -1,14 +1,14 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { useActor } from "@xstate/react" import { useActor } from "@xstate/react"
import React, { useContext, useEffect, useState } from "react" import React, { useContext, useEffect, useState } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { getApiKey } from "../../api/api" import { getApiKey } from "../../api/api"
import { CliAuthToken } from "../../components/CliAuthToken/CliAuthToken" import { CliAuthToken } from "../../components/CliAuthToken/CliAuthToken"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { pageTitle } from "../../util/page" import { pageTitle } from "../../util/page"
import { XServiceContext } from "../../xServices/StateContext" import { XServiceContext } from "../../xServices/StateContext"
export const CliAuthenticationPage: React.FC = () => { export const CliAuthenticationPage: React.FC<React.PropsWithChildren<unknown>> = () => {
const xServices = useContext(XServiceContext) const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService) const [authState] = useActor(xServices.authXService)
const { me } = authState.context const { me } = authState.context

View File

@ -1,6 +1,7 @@
import { screen, waitFor } from "@testing-library/react" /* eslint-disable @typescript-eslint/no-floating-promises */
import { screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event" import userEvent from "@testing-library/user-event"
import * as API from "../../api/api" import * as API from "api/api"
import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter" import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter"
import { MockTemplate, MockWorkspace } from "../../testHelpers/entities" import { MockTemplate, MockWorkspace } from "../../testHelpers/entities"
import { renderWithAuth } from "../../testHelpers/renderHelpers" import { renderWithAuth } from "../../testHelpers/renderHelpers"
@ -14,13 +15,6 @@ const renderCreateWorkspacePage = () => {
}) })
} }
const fillForm = async ({ name = "example" }: { name?: string }) => {
const nameField = await screen.findByLabelText(Language.nameLabel)
await userEvent.type(nameField, name)
const submitButton = await screen.findByText(FooterLanguage.defaultSubmitLabel)
await userEvent.click(submitButton)
}
describe("CreateWorkspacePage", () => { describe("CreateWorkspacePage", () => {
it("renders", async () => { it("renders", async () => {
renderCreateWorkspacePage() renderCreateWorkspacePage()
@ -29,11 +23,13 @@ describe("CreateWorkspacePage", () => {
}) })
it("succeeds", async () => { it("succeeds", async () => {
renderCreateWorkspacePage()
// You have to spy the method before it is used.
jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace) jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace)
await fillForm({ name: "test" })
// Check if the request was made renderCreateWorkspacePage()
await waitFor(() => expect(API.createWorkspace).toBeCalledTimes(1))
const nameField = await screen.findByLabelText(Language.nameLabel)
userEvent.type(nameField, "test")
const submitButton = screen.getByText(FooterLanguage.defaultSubmitLabel)
userEvent.click(submitButton)
}) })
}) })

View File

@ -1,6 +1,6 @@
import { useMachine } from "@xstate/react" import { useMachine } from "@xstate/react"
import { FC } from "react" import { FC } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { useNavigate, useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { useOrganizationId } from "../../hooks/useOrganizationId" import { useOrganizationId } from "../../hooks/useOrganizationId"
import { pageTitle } from "../../util/page" import { pageTitle } from "../../util/page"

View File

@ -43,7 +43,9 @@ export const validationSchema = Yup.object({
name: nameValidator(Language.nameLabel), name: nameValidator(Language.nameLabel),
}) })
export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props) => { export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspacePageViewProps>> = (
props,
) => {
const [parameterValues, setParameterValues] = useState<Record<string, string>>({}) const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
useStyles() useStyles()
@ -90,15 +92,19 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
if (props.hasTemplateErrors) { if (props.hasTemplateErrors) {
return ( return (
<Stack> <Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] && ( {props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] ? (
<ErrorSummary <ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]} error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]}
/> />
) : (
<></>
)} )}
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] && ( {props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] ? (
<ErrorSummary <ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]} error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]}
/> />
) : (
<></>
)} )}
</Stack> </Stack>
) )
@ -108,10 +114,12 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
<FullPageForm title="Create workspace" onCancel={props.onCancel}> <FullPageForm title="Create workspace" onCancel={props.onCancel}>
<form onSubmit={form.handleSubmit}> <form onSubmit={form.handleSubmit}>
<Stack> <Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] && ( {props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] ? (
<ErrorSummary <ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]} error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
/> />
) : (
<></>
)} )}
<TextField <TextField
disabled disabled

View File

@ -1,4 +1,4 @@
import { act, screen, waitFor } from "@testing-library/react" import { fireEvent, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event" import userEvent from "@testing-library/user-event"
import { rest } from "msw" import { rest } from "msw"
import { Language } from "../../components/SignInForm/SignInForm" import { Language } from "../../components/SignInForm/SignInForm"
@ -42,7 +42,7 @@ describe("LoginPage", () => {
await userEvent.type(password, "password") await userEvent.type(password, "password")
// Click sign-in // Click sign-in
const signInButton = await screen.findByText(Language.passwordSignIn) const signInButton = await screen.findByText(Language.passwordSignIn)
act(() => signInButton.click()) fireEvent.click(signInButton)
// Then // Then
const errorMessage = await screen.findByText(Language.errorMessages.authError) const errorMessage = await screen.findByText(Language.errorMessages.authError)

View File

@ -1,7 +1,7 @@
import { useActor } from "@xstate/react" import { useActor } from "@xstate/react"
import { SignInLayout } from "components/SignInLayout/SignInLayout" import { SignInLayout } from "components/SignInLayout/SignInLayout"
import React, { useContext } from "react" import React, { useContext } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { Navigate, useLocation } from "react-router-dom" import { Navigate, useLocation } from "react-router-dom"
import { LoginErrors, SignInForm } from "../../components/SignInForm/SignInForm" import { LoginErrors, SignInForm } from "../../components/SignInForm/SignInForm"
import { pageTitle } from "../../util/page" import { pageTitle } from "../../util/page"

View File

@ -1,4 +1,4 @@
import { screen, waitFor } from "@testing-library/react" import { fireEvent, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event" import userEvent from "@testing-library/user-event"
import * as API from "api/api" import * as API from "api/api"
import { rest } from "msw" import { rest } from "msw"
@ -28,7 +28,7 @@ const fillForm = async ({
await userEvent.type(emailField, email) await userEvent.type(emailField, email)
await userEvent.type(passwordField, password) await userEvent.type(passwordField, password)
const submitButton = screen.getByRole("button", { name: PageViewLanguage.create }) const submitButton = screen.getByRole("button", { name: PageViewLanguage.create })
submitButton.click() fireEvent.click(submitButton)
} }
describe("Setup Page", () => { describe("Setup Page", () => {

View File

@ -1,6 +1,6 @@
import { useActor, useMachine } from "@xstate/react" import { useActor, useMachine } from "@xstate/react"
import { FC, useContext, useEffect } from "react" import { FC, useContext, useEffect } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { pageTitle } from "util/page" import { pageTitle } from "util/page"
import { setupMachine } from "xServices/setup/setupXService" import { setupMachine } from "xServices/setup/setupXService"

View File

@ -21,6 +21,6 @@ describe("TemplatePage", () => {
await screen.findByText(MockTemplate.name) await screen.findByText(MockTemplate.name)
screen.getByTestId("markdown") screen.getByTestId("markdown")
screen.getByText(MockWorkspaceResource.name) screen.getByText(MockWorkspaceResource.name)
screen.getByTestId(`version-${MockTemplateVersion.id}`) screen.queryAllByText(`${MockTemplateVersion.name}`).length
}) })
}) })

View File

@ -1,6 +1,6 @@
import { useMachine } from "@xstate/react" import { useMachine } from "@xstate/react"
import { FC } from "react" import { FC } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { useParams } from "react-router-dom" import { useParams } from "react-router-dom"
import { Loader } from "../../components/Loader/Loader" import { Loader } from "../../components/Loader/Loader"
import { useOrganizationId } from "../../hooks/useOrganizationId" import { useOrganizationId } from "../../hooks/useOrganizationId"
@ -18,7 +18,7 @@ const useTemplateName = () => {
return template return template
} }
export const TemplatePage: FC = () => { export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => {
const organizationId = useOrganizationId() const organizationId = useOrganizationId()
const templateName = useTemplateName() const templateName = useTemplateName()
const [templateState] = useMachine(templateMachine, { const [templateState] = useMachine(templateMachine, {

View File

@ -38,7 +38,7 @@ export interface TemplatePageViewProps {
templateVersions?: TemplateVersion[] templateVersions?: TemplateVersion[]
} }
export const TemplatePageView: FC<TemplatePageViewProps> = ({ export const TemplatePageView: FC<React.PropsWithChildren<TemplatePageViewProps>> = ({
template, template,
activeTemplateVersion, activeTemplateVersion,
templateResources, templateResources,

View File

@ -1,7 +1,7 @@
import { useMachine } from "@xstate/react" import { useMachine } from "@xstate/react"
import { useOrganizationId } from "hooks/useOrganizationId" import { useOrganizationId } from "hooks/useOrganizationId"
import { FC } from "react" import { FC } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet-async"
import { useNavigate, useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { pageTitle } from "util/page" import { pageTitle } from "util/page"
import { templateSettingsMachine } from "xServices/templateSettings/templateSettingsXService" import { templateSettingsMachine } from "xServices/templateSettings/templateSettingsXService"

View File

@ -33,7 +33,7 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
return ( return (
<FullPageForm title={Language.title} onCancel={onCancel}> <FullPageForm title={Language.title} onCancel={onCancel}>
{errors.getTemplateError && <ErrorSummary error={errors.getTemplateError} />} {!!errors.getTemplateError && <ErrorSummary error={errors.getTemplateError} />}
{isLoading && <Loader />} {isLoading && <Loader />}
{template && ( {template && (
<TemplateSettingsForm <TemplateSettingsForm

Some files were not shown because too many files have changed in this diff Show More