From eb0a427c81e8238b18e3f59e157033c3271e0411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Mon, 4 Nov 2024 13:23:20 +0100 Subject: [PATCH] Clean up spacing in login modal in Connect (#48143) * Align ClusterLogin story with actual dialog presentation * Add stories for LocalError and SsoError * Share setup code between ClusterLogin and ClusterLoginReason * Use named Indicator export in design/index.ts * Align ClusterAdd story with actual presentation * Clean up spacing within ClusterConnect * Remove type assertions from storyHelpers.tsx * Remove bottom margin from login disabled alert --- web/packages/design/src/index.ts | 2 +- .../ClusterAdd/ClusterAdd.story.tsx | 48 ++++--- .../ClusterConnect/ClusterAdd/ClusterAdd.tsx | 23 ++-- .../src/ui/ClusterConnect/ClusterConnect.tsx | 9 +- .../ClusterLogin/ClusterLogin.story.tsx | 117 +++++++----------- .../ClusterLogin/ClusterLogin.tsx | 41 ++++-- .../ClusterLogin/ClusterLoginReason.story.tsx | 62 +--------- .../FormLogin/FormLocal/FormLocal.tsx | 35 +++--- .../ClusterLogin/FormLogin/FormLogin.tsx | 85 +++++++++---- .../FormLogin/FormSso/FormSso.tsx | 2 - .../FormLogin/FormSso/SsoButtons.tsx | 11 +- .../PromptPasswordless/PromptPasswordless.tsx | 2 +- .../PromptSsoStatus/PromptSsoStatus.tsx | 2 +- .../ClusterLogin/storyHelpers.tsx | 65 ++++++++++ .../teleterm/src/ui/ClusterConnect/spacing.ts | 38 ++++++ 15 files changed, 310 insertions(+), 232 deletions(-) create mode 100644 web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx create mode 100644 web/packages/teleterm/src/ui/ClusterConnect/spacing.ts diff --git a/web/packages/design/src/index.ts b/web/packages/design/src/index.ts index bcfe9ec3206b2..f5260ba2ac1c7 100644 --- a/web/packages/design/src/index.ts +++ b/web/packages/design/src/index.ts @@ -31,7 +31,7 @@ import ButtonLink from './ButtonLink'; import { ButtonWithMenu } from './ButtonWithMenu'; import Card from './Card'; import CardSuccess, { CardSuccessLogin } from './CardSuccess'; -import Indicator from './Indicator'; +import { Indicator } from './Indicator'; import Input from './Input'; import Label from './Label'; import LabelInput from './LabelInput'; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx index a895f63f4aa9a..163e56d13465e 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.story.tsx @@ -16,12 +16,16 @@ * along with this program. If not, see . */ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; + +import Dialog from 'design/Dialog'; import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; +import { dialogCss } from '../spacing'; + import { ClusterAdd } from './ClusterAdd'; import type * as tshd from 'teleterm/services/tshd/types'; @@ -33,11 +37,13 @@ export default { export const Story = () => { return ( - {}} - onCancel={() => {}} - /> + + {}} + onCancel={() => {}} + /> + ); }; @@ -45,11 +51,13 @@ export const Story = () => { export const WithPrefill = () => { return ( - {}} - onCancel={() => {}} - /> + + {}} + onCancel={() => {}} + /> + ); }; @@ -62,11 +70,13 @@ export const ErrorOnSubmit = () => { Promise.reject(new Error('Oops, something went wrong.')), })} > - {}} - onCancel={() => {}} - /> + + {}} + onCancel={() => {}} + /> + ); }; @@ -81,3 +91,9 @@ function getMockAppContext( args.addRootCluster || (() => Promise.resolve(makeRootCluster())); return appContext; } + +const Wrapper = ({ children }: PropsWithChildren) => ( + + {children} + +); diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx index ce8ca60a9798a..4a8239b5ec3df 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterAdd/ClusterAdd.tsx @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; +import { useState } from 'react'; import * as Alerts from 'design/Alert'; -import { Box, ButtonPrimary, ButtonSecondary, H2 } from 'design'; +import { Box, Flex, ButtonPrimary, ButtonSecondary, H2 } from 'design'; import FieldInput from 'shared/components/FieldInput'; import Validation from 'shared/components/Validation'; import { requiredField } from 'shared/components/Validation/rules'; @@ -27,6 +27,8 @@ import { useAsync } from 'shared/hooks/useAsync'; import { useAppContext } from 'teleterm/ui/appContextProvider'; +import { outermostPadding } from '../spacing'; + export function ClusterAdd(props: { onCancel(): void; onSuccess(clusterUri: string): void; @@ -43,7 +45,7 @@ export function ClusterAdd(props: { const [addr, setAddr] = useState(props.prefill.clusterAddress || ''); return ( - + {({ validator }) => (

Enter cluster address

- + {status === 'error' && ( - + Could not add the cluster )} @@ -65,15 +67,12 @@ export function ClusterAdd(props: { rule={requiredField('Cluster address is required')} value={addr} autoFocus + mb={0} onChange={e => setAddr(e.target.value)} placeholder="teleport.example.com" /> - - + + Next Cancel - + )} diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterConnect.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterConnect.tsx index cc92a0a57d466..5a88fd2f71a2c 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterConnect.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterConnect.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import React, { useState } from 'react'; +import { useState } from 'react'; import Dialog from 'design/Dialog'; @@ -26,6 +26,7 @@ import { RootClusterUri } from 'teleterm/ui/uri'; import { ClusterAdd } from './ClusterAdd'; import { ClusterLogin } from './ClusterLogin'; +import { dialogCss } from './spacing'; export function ClusterConnect(props: { dialog: DialogClusterConnect }) { const [createdClusterUri, setCreatedClusterUri] = useState< @@ -45,11 +46,7 @@ export function ClusterConnect(props: { dialog: DialogClusterConnect }) { return ( ({ - maxWidth: '480px', - width: '100%', - padding: '0', - })} + dialogCss={dialogCss} disableEscapeKeyDown={false} onClose={props.dialog.onCancel} open={true} diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx index 191ed831ab224..2500135faf616 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx @@ -15,62 +15,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { makeErrorAttempt, makeProcessingAttempt } from 'shared/hooks/useAsync'; -import { FC, PropsWithChildren } from 'react'; - -import { Box } from 'design'; -import { - Attempt, - makeErrorAttempt, - makeProcessingAttempt, -} from 'shared/hooks/useAsync'; - -import * as types from 'teleterm/ui/services/clusters/types'; - -import { - ClusterLoginPresentation, - ClusterLoginPresentationProps, -} from './ClusterLogin'; +import { ClusterLoginPresentation } from './ClusterLogin'; +import { TestContainer, makeProps } from './storyHelpers'; export default { title: 'Teleterm/ModalsHost/ClusterLogin', }; -function makeProps(): ClusterLoginPresentationProps { - return { - shouldPromptSsoStatus: false, - title: 'localhost', - loginAttempt: { - status: '', - statusText: '', - } as Attempt, - init: () => null, - initAttempt: { - status: 'success', - statusText: '', - data: { - localAuthEnabled: true, - authProviders: [], - type: '', - hasMessageOfTheDay: false, - allowPasswordless: true, - localConnectorName: '', - authType: 'local', - } as types.AuthSettings, - } as const, - - loggedInUserName: null, - onCloseDialog: () => null, - onAbort: () => null, - onLoginWithLocal: () => Promise.resolve<[void, Error]>([null, null]), - onLoginWithPasswordless: () => Promise.resolve<[void, Error]>([null, null]), - onLoginWithSso: () => null, - clearLoginAttempt: () => null, - passwordlessLoginState: null, - reason: undefined, - }; -} - export const LocalOnly = () => { const props = makeProps(); props.initAttempt.data.allowPasswordless = false; @@ -131,6 +84,19 @@ export const LocalProcessing = () => { ); }; +export const LocalError = () => { + const props = makeProps(); + props.initAttempt.data.allowPasswordless = false; + props.loginAttempt = makeErrorAttempt(new Error('invalid credentials')); + props.loggedInUserName = 'alice'; + + return ( + + + + ); +}; + const authProviders = [ { type: 'github', name: 'github', displayName: 'GitHub' }, { type: 'saml', name: 'microsoft', displayName: 'Microsoft' }, @@ -149,6 +115,32 @@ export const SsoOnly = () => { ); }; +export const SsoPrompt = () => { + const props = makeProps(); + props.loginAttempt.status = 'processing'; + props.shouldPromptSsoStatus = true; + return ( + + + + ); +}; + +export const SsoError = () => { + const props = makeProps(); + props.initAttempt.data.localAuthEnabled = false; + props.initAttempt.data.authType = 'github'; + props.initAttempt.data.authProviders = authProviders; + props.loginAttempt = makeErrorAttempt( + new Error("Failed to log in. Please check Teleport's log for more details.") + ); + return ( + + + + ); +}; + export const LocalWithPasswordless = () => { return ( @@ -308,28 +300,3 @@ export const HardwareCredentialPromptProcessing = () => { ); }; -export const SsoPrompt = () => { - const props = makeProps(); - props.loginAttempt.status = 'processing'; - props.shouldPromptSsoStatus = true; - return ( - - - - ); -}; - -const TestContainer: FC = ({ children }) => ( - <> - Bordered box is not part of the component - props.theme.colors.levels.elevated}; - background: ${props => props.theme.colors.levels.surface}; - `} - > - {children} - - -); diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx index 1909beaf3ebdd..72c173ccaf11c 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx @@ -18,7 +18,15 @@ import React from 'react'; import * as Alerts from 'design/Alert'; -import { ButtonIcon, Text, Indicator, Box, H2, ButtonPrimary } from 'design'; +import { + ButtonIcon, + Text, + Indicator, + Box, + Flex, + H2, + ButtonPrimary, +} from 'design'; import * as Icons from 'design/Icon'; import { DialogHeader, DialogContent } from 'design/Dialog'; import { PrimaryAuthType } from 'shared/services'; @@ -27,6 +35,8 @@ import { AuthSettings } from 'teleterm/ui/services/clusters/types'; import { ClusterConnectReason } from 'teleterm/ui/services/modals'; import { getTargetNameFromUri } from 'teleterm/services/tshd/gateway'; +import { outermostPadding } from '../spacing'; + import LoginForm from './FormLogin'; import useClusterLogin, { State, Props } from './useClusterLogin'; @@ -58,7 +68,7 @@ export function ClusterLoginPresentation({ }: ClusterLoginPresentationProps) { return ( <> - +

Log in to {title}

@@ -66,19 +76,32 @@ export function ClusterLoginPresentation({
- - {reason && } + + {reason && ( + + + + )} {initAttempt.status === 'error' && ( - - + + Unable to retrieve cluster auth preferences Retry - + )} {initAttempt.status === 'processing' && ( - + )} @@ -119,7 +142,7 @@ function Reason({ reason }: { reason: ClusterConnectReason }) { const $targetDesc = getTargetDesc(reason); return ( - + You tried to connect to {$targetDesc} but your session has expired. Please log in to refresh the session. diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLoginReason.story.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLoginReason.story.tsx index e6e53becffc97..ab6517f4cce67 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLoginReason.story.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLoginReason.story.tsx @@ -15,22 +15,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - -import { FC, PropsWithChildren } from 'react'; -import { Box } from 'design'; -import { Attempt } from 'shared/hooks/useAsync'; - -import * as types from 'teleterm/ui/services/clusters/types'; import { appUri, makeDatabaseGateway, makeKubeGateway, } from 'teleterm/services/tshd/testHelpers'; -import { - ClusterLoginPresentation, - ClusterLoginPresentationProps, -} from './ClusterLogin'; +import { TestContainer, makeProps } from './storyHelpers'; +import { ClusterLoginPresentation } from './ClusterLogin'; export default { title: 'Teleterm/ModalsHost/ClusterLogin/Reason', @@ -100,56 +92,6 @@ export const VnetCertExpired = () => { ); }; -function makeProps(): ClusterLoginPresentationProps { - return { - shouldPromptSsoStatus: false, - title: 'localhost', - loginAttempt: { - status: '', - statusText: '', - } as Attempt, - init: () => null, - initAttempt: { - status: 'success', - statusText: '', - data: { - localAuthEnabled: true, - authProviders: [], - type: '', - hasMessageOfTheDay: false, - allowPasswordless: true, - localConnectorName: '', - authType: 'local', - } as types.AuthSettings, - } as const, - - loggedInUserName: null, - onCloseDialog: () => null, - onAbort: () => null, - onLoginWithLocal: () => Promise.resolve<[void, Error]>([null, null]), - onLoginWithPasswordless: () => Promise.resolve<[void, Error]>([null, null]), - onLoginWithSso: () => null, - clearLoginAttempt: () => null, - passwordlessLoginState: null, - reason: undefined, - }; -} - -const TestContainer: FC = ({ children }) => ( - <> - Bordered box is not part of the component - props.theme.colors.levels.elevated}; - background: ${props => props.theme.colors.levels.surface}; - `} - > - {children} - - -); - const dbGateway = makeDatabaseGateway({ uri: '/gateways/gateway1', targetName: 'postgres', diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLocal/FormLocal.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLocal/FormLocal.tsx index 4a25bdf50f093..bf7decd149c93 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLocal/FormLocal.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLocal/FormLocal.tsx @@ -17,7 +17,7 @@ */ import React, { useState } from 'react'; -import { ButtonPrimary, Box } from 'design'; +import { ButtonPrimary, Flex } from 'design'; import Validation, { Validator } from 'shared/components/Validation'; import FieldInput from 'shared/components/FieldInput'; @@ -62,7 +62,7 @@ export const FormLocal = ({ return ( {({ validator }) => ( - + setUser(e.target.value)} placeholder="Username" - mb={3} + mb={0} disabled={isProcessing} /> setPass(e.target.value)} type="password" placeholder="Password" - mb={3} + mb={0} width="100%" disabled={isProcessing} /> - + + + )} ); diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLogin.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLogin.tsx index 378826a43712b..a5714014de6b5 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLogin.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormLogin.tsx @@ -24,6 +24,8 @@ import { Attempt } from 'shared/hooks/useAsync'; import * as types from 'teleterm/ui/services/clusters/types'; +import { outermostPadding } from '../../spacing'; + import { PromptPasswordless } from './PromptPasswordless'; import PromptSsoStatus from './PromptSsoStatus'; import { FormPasswordless } from './FormPasswordless'; @@ -46,12 +48,18 @@ export default function LoginForm(props: Props) { if (passwordlessLoginState) { return ( - + + + ); } if (shouldPromptSsoStatus) { - return ; + return ( + + + + ); } const ssoEnabled = authProviders?.length > 0; @@ -60,9 +68,9 @@ export default function LoginForm(props: Props) { // and display sso providers if any. if (!localAuthEnabled && ssoEnabled) { return ( - + {loginAttempt.status === 'error' && ( - + Could not log in )} @@ -73,8 +81,11 @@ export default function LoginForm(props: Props) { if (!localAuthEnabled) { return ( - - + + Login has not been enabled @@ -83,9 +94,16 @@ export default function LoginForm(props: Props) { // Everything below requires local auth to be enabled. return ( + // No extra padding so that StepSlider children can span the whole width of the parent + // component. This way when they slide, they slide from one side to the other, without + // disappearing behind padding. {loginAttempt.status === 'error' && ( - + Could not log in )} @@ -130,8 +148,13 @@ const Primary = ({ } return ( - - {$primary} + + {$primary} {otherOptionsAvailable && ( )} - + ); }; @@ -207,20 +230,24 @@ const Secondary = ({ } return ( - - {$secondary} - - { - otherProps.clearLoginAttempt(); - prev(); - }} - > - Back - - - + +
{$secondary}
+ { + otherProps.clearLoginAttempt(); + prev(); + }} + > + Back + +
); }; @@ -239,9 +266,11 @@ const Divider = () => ( ); -const FlexBordered = props => ( - -); +const FlexBordered = styled(Flex).attrs({ + justifyContent: 'center', + flexDirection: 'column', + gap: 3, +})``; const StyledOr = styled.div` background: ${props => props.theme.colors.levels.surface}; @@ -272,3 +301,5 @@ export type Props = types.AuthSettings & { onLogin(username: string, password: string): void; autoFocus?: boolean; }; + +const OutermostPadding = styled(Box).attrs({ px: outermostPadding })``; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/FormSso.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/FormSso.tsx index 9aa3474e0d36e..0464c1d22a6f1 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/FormSso.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/FormSso.tsx @@ -15,8 +15,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - -import React from 'react'; import { Box } from 'design'; import SSOButtonList from './SsoButtons'; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/SsoButtons.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/SsoButtons.tsx index 8c512317d5610..db8887f8fffd2 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/SsoButtons.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/FormSso/SsoButtons.tsx @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -import React from 'react'; -import { Box, Text } from 'design'; +import { Flex, Text } from 'design'; import ButtonSso, { guessProviderType } from 'shared/components/ButtonSso'; import * as types from 'teleterm/ui/services/clusters/types'; @@ -33,14 +32,12 @@ const SSOBtnList = ({ let { name, type, displayName } = item; const title = displayName || `${prefixText} ${name}`; const ssoType = guessProviderType(title, type as types.AuthProviderType); - const isLastItem = providers.length - 1 === index; return ( { e.preventDefault(); @@ -54,7 +51,11 @@ const SSOBtnList = ({ return You have no SSO providers configured; } - return {$btns}; + return ( + + {$btns} + + ); }; type Props = { diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptPasswordless/PromptPasswordless.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptPasswordless/PromptPasswordless.tsx index 83f3128172130..a7c94d9a7f0f4 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptPasswordless/PromptPasswordless.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptPasswordless/PromptPasswordless.tsx @@ -36,7 +36,7 @@ type Props = PasswordlessLoginState & { export function PromptPasswordless(props: Props) { const { prompt } = props; return ( - + {prompt === 'credential' && } {(prompt === 'tap' || prompt === 'retap') && } {prompt === 'pin' && } diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptSsoStatus/PromptSsoStatus.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptSsoStatus/PromptSsoStatus.tsx index 419dbb533f874..3ae7222352b61 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptSsoStatus/PromptSsoStatus.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/PromptSsoStatus/PromptSsoStatus.tsx @@ -22,7 +22,7 @@ import { LinearProgress } from 'teleterm/ui/components/LinearProgress'; export default function PromptSsoStatus(props: Props) { return ( - + Please follow the steps in the new browser window to authenticate. diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx new file mode 100644 index 0000000000000..54b9d94ff265b --- /dev/null +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx @@ -0,0 +1,65 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { FC, PropsWithChildren } from 'react'; +import Dialog from 'design/Dialog'; + +import { dialogCss } from '../spacing'; + +import { ClusterLoginPresentationProps } from './ClusterLogin'; + +export const TestContainer: FC = ({ children }) => ( + + {children} + +); + +export function makeProps(): ClusterLoginPresentationProps { + return { + shouldPromptSsoStatus: false, + title: 'localhost', + loginAttempt: { + status: '', + statusText: '', + data: undefined, + }, + init: () => null, + initAttempt: { + status: 'success', + statusText: '', + data: { + localAuthEnabled: true, + authProviders: [], + hasMessageOfTheDay: false, + allowPasswordless: true, + localConnectorName: '', + authType: 'local', + }, + }, + + loggedInUserName: null, + onCloseDialog: () => null, + onAbort: () => null, + onLoginWithLocal: () => Promise.resolve<[void, Error]>([null, null]), + onLoginWithPasswordless: () => Promise.resolve<[void, Error]>([null, null]), + onLoginWithSso: () => null, + clearLoginAttempt: () => null, + passwordlessLoginState: null, + reason: undefined, + }; +} diff --git a/web/packages/teleterm/src/ui/ClusterConnect/spacing.ts b/web/packages/teleterm/src/ui/ClusterConnect/spacing.ts new file mode 100644 index 0000000000000..293e806f54554 --- /dev/null +++ b/web/packages/teleterm/src/ui/ClusterConnect/spacing.ts @@ -0,0 +1,38 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import type { StyleFunction } from 'styled-components'; + +/** + * outermostPadding is the padding used in the visually outermost layer of ClusterConnect. + * Components inside should use spacing of value 3 or lower, otherwise individual elements might + * appear too far from each other. + */ +export const outermostPadding = 4; + +type NoExtraProps = Record; +/** + * dialogCss needs to avoid setting horizontal padding. This is so that child StepSlider components + * in ClusterLogin are able to span the whole width of the parent component. This way when they + * slide, they slide from one slide to another, without disappearing behind padding. + */ +export const dialogCss: StyleFunction = props => + ` +max-width: 480px; +width: 100%; +padding: ${props.theme.space[outermostPadding]}px 0 ${props.theme.space[outermostPadding]}px 0; +`;