fix: Design QA changes & form login removed from signup screen if disabled (#11958)

* design qa changes & form login removed from signup screen if disabled

* updated color to use constants & updated error message on restart banner

* added PR review changes

* reverted restart banner error message change

* updated restart timeout from 1 min to 2 min

* reduced spacing for disconnect button
This commit is contained in:
Ankita Kinger 2022-03-24 12:35:00 +05:30 committed by GitHub
parent fae7fc6e7c
commit 6df2c5eeb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 317 additions and 100 deletions

View File

@ -677,6 +677,9 @@ export const DISCONNECT_SERVICE_SUBHEADER = () =>
"Changes to this section can disrupt user authentication. Proceed with caution.";
export const DISCONNECT_SERVICE_WARNING = () =>
"will be removed as primary method of authentication";
export const AUTHENTICATION_METHOD_ENABLED = (methodName: string) => `
${methodName} authentication method is enabled
`;
export const DISCONNECT_EXISTING_REPOSITORIES = () =>
"Disconnect existing Repositories";
@ -1007,6 +1010,8 @@ export const ADMIN_AUTH_SETTINGS_TITLE = () => "Select Authentication Method";
export const ADMIN_AUTH_SETTINGS_SUBTITLE = () =>
"Select a protocol you want to authenticate users with";
export const DANGER_ZONE = () => "Danger Zone";
export const DISCONNECT_AUTH_METHOD = () => "Disconnect";
export const DISCONNECT_CONFIRMATION = () => "Are you sure?";
// Guided tour
// -- STEPS ---

View File

@ -11,6 +11,7 @@ import {
EDIT,
UPGRADE,
UPGRADE_TO_EE,
AUTHENTICATION_METHOD_ENABLED,
} from "@appsmith/constants/messages";
import { getAdminSettingsCategoryUrl } from "constants/routes";
import { Callout, CalloutType } from "components/ads/CalloutV2";
@ -20,6 +21,8 @@ import { useSelector } from "react-redux";
import bootIntercom from "utils/bootIntercom";
import { Colors } from "constants/Colors";
import Icon from "components/ads/Icon";
import TooltipComponent from "components/ads/Tooltip";
import { Position } from "@blueprintjs/core";
const { intercomAppID } = getAppsmithConfigs();
@ -122,7 +125,7 @@ const Label = styled.span<{ enterprise?: boolean }>`
background: #fff;
`
: `
color: #03B365;
color: ${Colors.GREEN};
background: #E5F6EC;
`};
padding: 0px 4px;
@ -170,7 +173,19 @@ export function AuthPage({ authMethods }: { authMethods: AuthMethodType[] }) {
</>
)}
{method.isConnected && (
<Icon fillColor="#03B365" name="oval-check" />
<TooltipComponent
autoFocus={false}
content={createMessage(
AUTHENTICATION_METHOD_ENABLED,
method.label,
)}
hoverOpenDelay={0}
minWidth={"180px"}
openOnTargetFocus={false}
position={Position.RIGHT}
>
<Icon fillColor={Colors.GREEN} name="oval-check" />
</TooltipComponent>
)}
</MethodTitle>
<MethodDets>{method.subText}</MethodDets>

View File

@ -94,6 +94,7 @@ export function SignUp(props: SignUpFormProps) {
const { emailValue: email, error, pristine, submitting, valid } = props;
const isFormValid = valid && email && !isEmptyString(email);
const socialLoginList = ThirdPartyLoginRegistry.get();
const shouldDisableSignupButton = pristine || !isFormValid;
const location = useLocation();
const recaptchaStatus = useScript(
@ -119,6 +120,32 @@ export function SignUp(props: SignUpFormProps) {
}
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formElement: HTMLFormElement = document.getElementById(
"signup-form",
) as HTMLFormElement;
if (
googleRecaptchaSiteKey.enabled &&
recaptchaStatus === ScriptStatus.READY
) {
window.grecaptcha
.execute(googleRecaptchaSiteKey.apiKey, {
action: "submit",
})
.then(function(token: any) {
formElement &&
formElement.setAttribute(
"action",
`${signupURL}?recaptchaToken=${token}`,
);
formElement && formElement.submit();
});
} else {
formElement && formElement.submit();
}
};
return (
<>
{showError && <FormMessage intent="danger" message={errorMessage} />}
@ -137,78 +164,57 @@ export function SignUp(props: SignUpFormProps) {
{socialLoginList.length > 0 && (
<ThirdPartyAuth logins={socialLoginList} type={"SIGNUP"} />
)}
<SpacedSubmitForm
action={signupURL}
id="signup-form"
method="POST"
onSubmit={(e) => {
e.preventDefault();
const formElement: HTMLFormElement = document.getElementById(
"signup-form",
) as HTMLFormElement;
if (
googleRecaptchaSiteKey.enabled &&
recaptchaStatus === ScriptStatus.READY
) {
window.grecaptcha
.execute(googleRecaptchaSiteKey.apiKey, {
action: "submit",
})
.then(function(token: any) {
formElement &&
formElement.setAttribute(
"action",
`${signupURL}?recaptchaToken=${token}`,
);
formElement && formElement.submit();
});
} else {
formElement && formElement.submit();
}
return false;
}}
>
<FormGroup
intent={error ? "danger" : "none"}
label={createMessage(SIGNUP_PAGE_EMAIL_INPUT_LABEL)}
{!disableLoginForm && (
<SpacedSubmitForm
action={signupURL}
id="signup-form"
method="POST"
onSubmit={(e) => handleSubmit(e)}
>
<FormTextField
autoFocus
name="email"
placeholder={createMessage(SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER)}
type="email"
/>
</FormGroup>
<FormGroup
intent={error ? "danger" : "none"}
label={createMessage(SIGNUP_PAGE_PASSWORD_INPUT_LABEL)}
>
<FormTextField
name="password"
placeholder={createMessage(SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER)}
type="password"
/>
</FormGroup>
<FormActions>
<Button
disabled={pristine || !isFormValid}
fill
isLoading={submitting}
onClick={() => {
AnalyticsUtil.logEvent("SIGNUP_CLICK", {
signupMethod: "EMAIL",
});
PerformanceTracker.startTracking(
PerformanceTransactionName.SIGN_UP,
);
}}
size={Size.large}
tag="button"
text={createMessage(SIGNUP_PAGE_SUBMIT_BUTTON_TEXT)}
type="submit"
/>
</FormActions>
</SpacedSubmitForm>
<FormGroup
intent={error ? "danger" : "none"}
label={createMessage(SIGNUP_PAGE_EMAIL_INPUT_LABEL)}
>
<FormTextField
autoFocus
name="email"
placeholder={createMessage(SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER)}
type="email"
/>
</FormGroup>
<FormGroup
intent={error ? "danger" : "none"}
label={createMessage(SIGNUP_PAGE_PASSWORD_INPUT_LABEL)}
>
<FormTextField
name="password"
placeholder={createMessage(
SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER,
)}
type="password"
/>
</FormGroup>
<FormActions>
<Button
disabled={shouldDisableSignupButton}
fill
isLoading={submitting}
onClick={() => {
AnalyticsUtil.logEvent("SIGNUP_CLICK", {
signupMethod: "EMAIL",
});
PerformanceTracker.startTracking(
PerformanceTransactionName.SIGN_UP,
);
}}
size={Size.large}
tag="button"
text={createMessage(SIGNUP_PAGE_SUBMIT_BUTTON_TEXT)}
type="submit"
/>
</FormActions>
</SpacedSubmitForm>
)}
</>
);
}

View File

@ -74,7 +74,7 @@ function RedirectUrlForm(
<TooltipComponent
autoFocus={false}
content={createMessage(REDIRECT_URL_TOOLTIP)}
hoverOpenDelay={1000}
hoverOpenDelay={0}
minWidth={"180px"}
openOnTargetFocus={false}
position={Position.RIGHT}

View File

@ -20,9 +20,15 @@ const InputCopyWrapper = styled.div`
display: flex;
align-items: center;
input {
width: 40rem;
}
svg {
margin-left: 12px;
cursor: pointer;
position: absolute;
right: -24px;
}
`;

View File

@ -1,9 +1,14 @@
import React from "react";
import React, { useState } from "react";
import styled from "styled-components";
import { Variant } from "components/ads/common";
import Button from "components/ads/Button";
import { Callout } from "components/ads/CalloutV2";
import { createMessage, DANGER_ZONE } from "@appsmith/constants/messages";
import {
createMessage,
DANGER_ZONE,
DISCONNECT_AUTH_METHOD,
DISCONNECT_CONFIRMATION,
} from "@appsmith/constants/messages";
import { Colors } from "constants/Colors";
import { getTypographyByKey } from "constants/DefaultTheme";
@ -15,11 +20,16 @@ export const Container = styled.div`
export const DisconnectButton = styled(Button)`
display: inline-block;
padding: 10px 20px;
min-width: 152px;
text-align: center;
font-size: 13px;
height: 38px;
margin-top: 16px;
background: ${Colors.CRIMSON};
border: 2px solid ${Colors.CRIMSON};
&:hover {
border: 2px solid ${Colors.CRIMSON};
}
`;
export const Header = styled.h2`
@ -43,14 +53,22 @@ export function DisconnectService(props: {
subHeader: string;
warning: string;
}) {
const [warnDisconnectAuth, setWarnDisconnectAuth] = useState(false);
return (
<Container>
<HeaderDanger>{createMessage(DANGER_ZONE)}</HeaderDanger>
<Info>{props.subHeader}</Info>
<Callout title={props.warning} type="Warning" />
<DisconnectButton
onClick={props.disconnect}
text="Disconnect"
onClick={() =>
warnDisconnectAuth ? props.disconnect() : setWarnDisconnectAuth(true)
}
text={
warnDisconnectAuth
? createMessage(DISCONNECT_CONFIRMATION)
: createMessage(DISCONNECT_AUTH_METHOD)
}
variant={Variant.danger}
/>
</Container>

View File

@ -9,7 +9,7 @@ import { FormGroup, SettingComponentProps } from "./Common";
const ButtonWrapper = styled.div`
width: 357px;
margin-bottom: ${(props) => props.theme.spaces[12]}px;
margin-bottom: ${(props) => props.theme.spaces[11]}px;
margin-top: 3px;
`;

View File

@ -62,9 +62,11 @@ export function FormGroup({ children, className, setting }: FieldHelperProps) {
className={className}
data-testid="admin-settings-form-group"
>
<StyledLabel data-testid="admin-settings-form-group-label">
{createMessage(() => setting.label || "")}
</StyledLabel>
{setting.label && (
<StyledLabel data-testid="admin-settings-form-group-label">
{createMessage(() => setting.label || "")}
</StyledLabel>
)}
{setting.isRequired && <StyledAsterisk>*</StyledAsterisk>}
{setting.helpText && (
<Tooltip content={createMessage(() => setting.helpText || "")}>

View File

@ -40,8 +40,23 @@ function getElements() {
const text = screen.queryAllByTestId("admin-settings-group-text");
const button = screen.queryAllByTestId("admin-settings-group-button");
const group = screen.queryAllByTestId("admin-settings-group");
const tagInput = screen.queryAllByTestId("admin-settings-tag-input");
const accordion = screen.queryAllByTestId("admin-settings-accordion");
const uneditableField = screen.queryAllByTestId(
"admin-settings-uneditable-field",
);
return { textInput, toggle, link, text, button, group };
return {
textInput,
toggle,
link,
text,
button,
group,
tagInput,
accordion,
uneditableField,
};
}
describe("Group", () => {
@ -59,72 +74,225 @@ describe("Group", () => {
it("is rendered for text input", () => {
settings[0].controlType = SettingTypes.TEXTINPUT;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(1);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for toggle", () => {
settings[0].controlType = SettingTypes.TOGGLE;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(1);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for link", () => {
settings[0].controlType = SettingTypes.LINK;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(1);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for text", () => {
settings[0].controlType = SettingTypes.TEXT;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(1);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for button", () => {
settings[0].controlType = SettingTypes.BUTTON;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(1);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for group", () => {
settings[0].controlType = SettingTypes.GROUP;
renderComponent();
const { button, group, link, text, textInput, toggle } = getElements();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(1);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for uneditable field", () => {
settings[0].controlType = SettingTypes.UNEDITABLEFIELD;
renderComponent();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(1);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(0);
});
it("is rendered for tag input", () => {
settings[0].controlType = SettingTypes.TAGINPUT;
renderComponent();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(1);
expect(accordion).toHaveLength(0);
});
it("is rendered for accordion", () => {
settings[0].controlType = SettingTypes.ACCORDION;
renderComponent();
const {
accordion,
button,
group,
link,
tagInput,
text,
textInput,
toggle,
uneditableField,
} = getElements();
expect(textInput).toHaveLength(0);
expect(toggle).toHaveLength(0);
expect(link).toHaveLength(0);
expect(text).toHaveLength(0);
expect(button).toHaveLength(0);
expect(group).toHaveLength(0);
expect(uneditableField).toHaveLength(0);
expect(tagInput).toHaveLength(0);
expect(accordion).toHaveLength(1);
});
});

View File

@ -10,12 +10,13 @@ import { FormTextFieldProps } from "components/ads/formFields/TextField";
import Toggle from "components/ads/Toggle";
import { createMessage } from "@appsmith/constants/messages";
const ToggleWrapper = styled.div``;
const ToggleWrapper = styled.div`
display: flex;
margin-bottom: 8px;
`;
const ToggleStatus = styled.span`
position: relative;
top: -10px;
left: 68px;
margin-left: 64px;
`;
function FieldToggleWithToggleText(toggleText?: (value: boolean) => string) {
@ -49,10 +50,6 @@ function FieldToggleWithToggleText(toggleText?: (value: boolean) => string) {
const StyledFieldToggleGroup = styled.div`
margin-bottom: 8px;
& .slider {
margin-top: 10px;
}
`;
export function ToggleComponent({ setting }: SettingComponentProps) {

View File

@ -178,7 +178,7 @@ export default function Group({
return (
<div
className={setting.isHidden ? "hide" : ""}
data-testid="admin-settings-redirect-url"
data-testid="admin-settings-uneditable-field"
key={setting.name || setting.id}
>
<RedirectUrlReduxForm

View File

@ -84,7 +84,7 @@ function* SaveAdminSettingsSaga(action: ReduxAction<Record<string, string>>) {
}
}
const RESTART_POLL_TIMEOUT = 60000;
const RESTART_POLL_TIMEOUT = 2 * 60 * 1000;
const RESTART_POLL_INTERVAL = 2000;
function* RestartServerPoll() {