diff --git a/app/client/packages/design-system/ads/src/Input/Input.constants.ts b/app/client/packages/design-system/ads/src/Input/Input.constants.ts index 8afb9e65b1..a7d0351fcb 100644 --- a/app/client/packages/design-system/ads/src/Input/Input.constants.ts +++ b/app/client/packages/design-system/ads/src/Input/Input.constants.ts @@ -9,3 +9,4 @@ export const InputStartIconClassName = `${InputIconClassName}-start`; export const InputEndIconClassName = `${InputIconClassName}-end`; export const InputStartIconDisabledClassName = `${InputStartIconClassName}-disabled`; export const InputEndIconDisabledClassName = `${InputEndIconClassName}-disabled`; +export const InputPostfixClassName = `${InputClassName}-postfix`; diff --git a/app/client/packages/design-system/ads/src/Input/Input.styles.tsx b/app/client/packages/design-system/ads/src/Input/Input.styles.tsx index 2b33df8320..366d062191 100644 --- a/app/client/packages/design-system/ads/src/Input/Input.styles.tsx +++ b/app/client/packages/design-system/ads/src/Input/Input.styles.tsx @@ -5,6 +5,7 @@ import { InputIconClassName, InputStartIconClassName, InputStartIconDisabledClassName, + InputPostfixClassName, } from "./Input.constants"; import type { InputSizes } from "./Input.types"; import { Text } from "../Text"; @@ -129,6 +130,15 @@ export const InputContainer = styled.div<{ opacity: var(--ads-v2-opacity-disabled); cursor: not-allowed !important; } + + .${InputPostfixClassName} { + position: absolute; + right: var(--ads-v2-spaces-3); + display: flex; + align-items: center; + pointer-events: none; + color: var(--ads-v2-colors-content-label-default); + } `; export const StyledInput = styled.input<{ @@ -139,6 +149,8 @@ export const StyledInput = styled.input<{ hasEndIcon?: boolean; renderer?: "input" | "textarea"; inputSize?: InputSizes; + hasPostfix?: boolean; + postfixSize?: number; }>` --icon-size: ${({ inputSize }) => inputSize && iconSizes[inputSize]}; @@ -176,6 +188,13 @@ export const StyledInput = styled.input<{ ); `} + /* Additional padding for postfix */ + ${({ hasPostfix, postfixSize }) => + hasPostfix && + css` + padding-right: calc(var(--input-padding-x) + ${postfixSize}ch); + `} + &:hover:enabled:not(:read-only) { --input-color-border: var(--ads-v2-colors-control-field-hover-border); } diff --git a/app/client/packages/design-system/ads/src/Input/Input.tsx b/app/client/packages/design-system/ads/src/Input/Input.tsx index c3c57afda2..98e746c50a 100644 --- a/app/client/packages/design-system/ads/src/Input/Input.tsx +++ b/app/client/packages/design-system/ads/src/Input/Input.tsx @@ -22,6 +22,7 @@ import { InputEndIconClassName, InputIconClassName, InputStartIconClassName, + InputPostfixClassName, } from "./Input.constants"; const Input = forwardRef( @@ -40,6 +41,7 @@ const Input = forwardRef( label, labelPosition = "top", onChange, + postfix, renderAs = "input", size = "sm", startIcon, @@ -125,30 +127,47 @@ const Input = forwardRef( data-is-valid={isValid} disabled={disableTextInput || isDisabled} hasEndIcon={!!endIcon} + hasPostfix={!!postfix} hasStartIcon={!!startIcon} inputSize={size} onChange={handleOnChange} + postfixSize={postfix?.length} readOnly={isReadOnly} ref={inputRef} renderer={renderAs} value={value} {...rest} /> - {/* End Icon Section */} - {endIcon && renderAs === "input" ? ( - + {postfix && ( + + {postfix} + )} - data-has-onclick={!!endIconOnClick} - name={endIcon} - onClick={endIconOnClick} - size={size} - {...restOfEndIconProps} - /> - ) : null} + {endIcon && ( + + )} + + )} {description && ( `Sign up`; export const ALREADY_HAVE_AN_ACCOUNT = () => `Already have an account?`; export const LOOKING_TO_SELF_HOST = () => "Looking to self-host Appsmith?"; export const VISIT_OUR_DOCS = () => "Visit our docs"; +export const ALREADY_USING_APPSMITH = () => `Already using Appsmith?`; +export const SIGN_IN_TO_AN_EXISTING_ORGANISATION = () => + `Sign in to an existing organisation`; export const SIGNUP_PAGE_SUCCESS = () => `Awesome! You have successfully registered.`; @@ -1576,6 +1579,7 @@ export const WELCOME_FORM_NON_SUPER_USER_USE_CASE = () => "What would you like to use Appsmith for?"; export const WELCOME_FORM_NON_SUPER_USER_PROFICIENCY_LEVEL = () => "What is your general development proficiency?"; +export const WELCOME_FORM_FULL_NAME = () => "What’s your full name?"; export const WELCOME_FORM_PROFICIENCY_ERROR_MESSAGE = () => "Please select a proficiency level"; diff --git a/app/client/src/ce/entities/FeatureFlag.ts b/app/client/src/ce/entities/FeatureFlag.ts index 13fd0ce62a..b178e02dd4 100644 --- a/app/client/src/ce/entities/FeatureFlag.ts +++ b/app/client/src/ce/entities/FeatureFlag.ts @@ -53,6 +53,7 @@ export const FEATURE_FLAG = { "release_external_saas_plugins_enabled", release_tablev2_infinitescroll_enabled: "release_tablev2_infinitescroll_enabled", + license_multi_org_enabled: "license_multi_org_enabled", release_table_custom_sort_function_enabled: "release_table_custom_sort_function_enabled", } as const; @@ -99,6 +100,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = { release_ads_entity_item_enabled: false, release_external_saas_plugins_enabled: false, release_tablev2_infinitescroll_enabled: false, + license_multi_org_enabled: false, release_table_custom_sort_function_enabled: false, }; diff --git a/app/client/src/components/utils/ReduxFormTextField.tsx b/app/client/src/components/utils/ReduxFormTextField.tsx index d012864f12..c14dce0c5e 100644 --- a/app/client/src/components/utils/ReduxFormTextField.tsx +++ b/app/client/src/components/utils/ReduxFormTextField.tsx @@ -65,6 +65,7 @@ export interface FormTextFieldProps { // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any parse?: (value: any) => any; + postfix?: string; } function ReduxFormTextField(props: FormTextFieldProps) { diff --git a/app/client/src/constants/routes/baseRoutes.ts b/app/client/src/constants/routes/baseRoutes.ts index 4b0a059c63..402924604c 100644 --- a/app/client/src/constants/routes/baseRoutes.ts +++ b/app/client/src/constants/routes/baseRoutes.ts @@ -29,6 +29,7 @@ export const WORKSPACE_SETTINGS_PAGE_URL = `${WORKSPACE_URL}/settings`; export const WORKSPACE_SETTINGS_GENERAL_PAGE_URL = `${WORKSPACE_URL}/settings/general`; export const WORKSPACE_SETTINGS_MEMBERS_PAGE_URL = `${WORKSPACE_URL}/settings/members`; export const WORKSPACE_SETTINGS_LICENSE_PAGE_URL = `/settings/license`; +export const ORG_LOGIN_PATH = "/org"; export const matchApplicationPath = match(APPLICATIONS_URL); export const matchTemplatesPath = match(TEMPLATES_PATH); diff --git a/app/client/src/hooks/index.ts b/app/client/src/hooks/index.ts new file mode 100644 index 0000000000..a33aae5ae8 --- /dev/null +++ b/app/client/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useIsCloudBillingEnabled"; diff --git a/app/client/src/hooks/useIsCloudBillingEnabled.ts b/app/client/src/hooks/useIsCloudBillingEnabled.ts new file mode 100644 index 0000000000..370e5e2df5 --- /dev/null +++ b/app/client/src/hooks/useIsCloudBillingEnabled.ts @@ -0,0 +1,15 @@ +import { useSelector } from "react-redux"; + +import { getAppsmithConfigs } from "ee/configs"; +import { getIsCloudBillingFeatureFlagEnabled } from "selectors/cloudBillingSelectors"; + +const useIsCloudBillingEnabled = () => { + const { cloudHosting } = getAppsmithConfigs(); + const isCloudBillingFeatureFlagEnabled = useSelector( + getIsCloudBillingFeatureFlagEnabled, + ); + + return isCloudBillingFeatureFlagEnabled && cloudHosting; +}; + +export { useIsCloudBillingEnabled }; diff --git a/app/client/src/pages/UserAuth/SignUp.tsx b/app/client/src/pages/UserAuth/SignUp.tsx index c18b3334ce..255b05e740 100644 --- a/app/client/src/pages/UserAuth/SignUp.tsx +++ b/app/client/src/pages/UserAuth/SignUp.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import type { InjectedFormProps } from "redux-form"; import { reduxForm, formValueSelector } from "redux-form"; -import { AUTH_LOGIN_URL } from "constants/routes"; +import { AUTH_LOGIN_URL, ORG_LOGIN_PATH } from "constants/routes"; import { SIGNUP_FORM_NAME } from "ee/constants/forms"; import type { RouteComponentProps } from "react-router-dom"; import { useHistory, useLocation, withRouter } from "react-router-dom"; @@ -26,6 +26,8 @@ import { GOOGLE_RECAPTCHA_KEY_ERROR, LOOKING_TO_SELF_HOST, VISIT_OUR_DOCS, + ALREADY_USING_APPSMITH, + SIGN_IN_TO_AN_EXISTING_ORGANISATION, } from "ee/constants/messages"; import FormTextField from "components/utils/ReduxFormTextField"; import ThirdPartyAuth from "pages/UserAuth/ThirdPartyAuth"; @@ -59,6 +61,8 @@ import log from "loglevel"; import { SELF_HOSTING_DOC } from "constants/ThirdPartyConstants"; import * as Sentry from "@sentry/react"; import CsrfTokenInput from "pages/UserAuth/CsrfTokenInput"; +import { useIsCloudBillingEnabled } from "hooks"; +import { isLoginHostname } from "utils/cloudBillingUtils"; declare global { interface Window { @@ -122,6 +126,8 @@ export function SignUp(props: SignUpFormProps) { const organizationConfig = useSelector(getOrganizationConfig); const { instanceName } = organizationConfig; const htmlPageTitle = getHTMLPageTitle(isBrandingEnabled, instanceName); + const isCloudBillingEnabled = useIsCloudBillingEnabled(); + const isHostnameEqualtoLogin = isLoginHostname(); const recaptchaStatus = useScript( `https://www.google.com/recaptcha/api.js?render=${googleRecaptchaSiteKey.apiKey}`, @@ -195,17 +201,31 @@ export function SignUp(props: SignUpFormProps) { const footerSection = ( <> -
- {createMessage(ALREADY_HAVE_AN_ACCOUNT)}  - - {createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)} - -
+ {isCloudBillingEnabled && isHostnameEqualtoLogin ? ( +
+ {createMessage(ALREADY_USING_APPSMITH)} + + {createMessage(SIGN_IN_TO_AN_EXISTING_ORGANISATION)} + +
+ ) : ( +
+ {createMessage(ALREADY_HAVE_AN_ACCOUNT)}  + + {createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)} + +
+ )} {cloudHosting && ( <> or diff --git a/app/client/src/pages/setup/NonSuperUserProfilingQuestions.tsx b/app/client/src/pages/setup/NonSuperUserProfilingQuestions.tsx index 324edde6d1..67195c2715 100644 --- a/app/client/src/pages/setup/NonSuperUserProfilingQuestions.tsx +++ b/app/client/src/pages/setup/NonSuperUserProfilingQuestions.tsx @@ -12,6 +12,7 @@ import { WELCOME_FORM_NON_SUPER_USER_PROFICIENCY_LEVEL, WELCOME_FORM_PROFICIENCY_ERROR_MESSAGE, WELCOME_FORM_USE_CASE_ERROR_MESSAGE, + WELCOME_FORM_FULL_NAME, } from "ee/constants/messages"; import { connect } from "react-redux"; import type { AppState } from "ee/reducers"; @@ -20,6 +21,8 @@ import { Field, formValueSelector, reduxForm } from "redux-form"; import styled from "styled-components"; import { proficiencyOptions, useCaseOptions } from "./constants"; import RadioButtonGroup from "components/editorComponents/RadioButtonGroup"; +import FormTextField from "components/utils/ReduxFormTextField"; +import { useIsCloudBillingEnabled } from "hooks"; const ActionContainer = styled.div` margin-top: ${(props) => props.theme.spaces[15]}px; @@ -64,13 +67,26 @@ const validate = (values: any) => { function NonSuperUserProfilingQuestions( props: InjectedFormProps & UserFormProps & NonSuperUserFormData, ) { + const isCloudBillingEnabled = useIsCloudBillingEnabled(); + const onSubmit = (data: NonSuperUserFormData) => { props.onGetStarted && props.onGetStarted(data.proficiency, data.useCase); }; return (
- + {isCloudBillingEnabled && ( + <> + + + + + )} featureFlags.license_multi_org_enabled, +); diff --git a/app/client/src/utils/cloudBillingUtils.ts b/app/client/src/utils/cloudBillingUtils.ts new file mode 100644 index 0000000000..7513989be5 --- /dev/null +++ b/app/client/src/utils/cloudBillingUtils.ts @@ -0,0 +1,5 @@ +export function isLoginHostname(): boolean { + const hostname = window.location.hostname; + + return hostname.split(".")[0] === "login"; +}