feat: Cloud billing static UI (#39846)
## Description This PR contains static UI for cloud billing. https://www.figma.com/design/XouAwUQJKF2lf57bQvNM1e/Cloud-Billing-(-Phase-1-)?node-id=15907-14530&t=ogZ4sTrvsBdvVL8m-0 Fixes #https://github.com/appsmithorg/appsmith/issues/39896 ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!CAUTION] > 🔴 🔴 🔴 Some tests have failed. > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/14077392797> > Commit: 73f7f2d1bee5a6bf3547af050c1fa6172052cc6e > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14077392797&attempt=2&selectiontype=test&testsstatus=failed&specsstatus=fail" target="_blank">Cypress dashboard</a>. > Tags: @tag.All > Spec: > The following are new failures, please fix them before merging the PR: <ol> > <li>cypress/e2e/Regression/ClientSide/FormLogin/EnableFormLogin_spec.js</ol> > <a href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master" target="_blank">List of identified flaky tests</a>. > <hr>Wed, 26 Mar 2025 08:34:53 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced a postfix functionality for input fields, allowing additional content to be displayed alongside the standard input. - Enhanced styling to dynamically adjust input spacing when a postfix is provided. - Extended form input components to support the optional postfix property for greater flexibility in content presentation. - Added a feature flag for multi-organization licensing management. - Introduced a custom hook to check if cloud billing is enabled. - Added new user interface messages for account management in the signup process. - Implemented a new route for organizational login. - Added a conditional input field for full name based on cloud billing status. - **Bug Fixes** - Improved rendering logic for end icons and postfix elements in input fields. - **Documentation** - Updated export statements to streamline access to new hooks and selectors. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
dc079f0d8d
commit
e3a49845e7
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
InputEndIconClassName,
|
||||
InputIconClassName,
|
||||
InputStartIconClassName,
|
||||
InputPostfixClassName,
|
||||
} from "./Input.constants";
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
|
|
@ -40,6 +41,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||
label,
|
||||
labelPosition = "top",
|
||||
onChange,
|
||||
postfix,
|
||||
renderAs = "input",
|
||||
size = "sm",
|
||||
startIcon,
|
||||
|
|
@ -125,30 +127,47 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||
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" ? (
|
||||
<Icon
|
||||
className={clsx(
|
||||
InputIconClassName,
|
||||
InputEndIconClassName,
|
||||
endIconClassName,
|
||||
{/* End Icon/Postfix Section */}
|
||||
{renderAs === "input" && (
|
||||
<>
|
||||
{postfix && (
|
||||
<span
|
||||
className={clsx(InputPostfixClassName)}
|
||||
style={{
|
||||
color: "var(--ads-v2-colors-content-label-default)",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
{postfix}
|
||||
</span>
|
||||
)}
|
||||
data-has-onclick={!!endIconOnClick}
|
||||
name={endIcon}
|
||||
onClick={endIconOnClick}
|
||||
size={size}
|
||||
{...restOfEndIconProps}
|
||||
/>
|
||||
) : null}
|
||||
{endIcon && (
|
||||
<Icon
|
||||
className={clsx(
|
||||
InputIconClassName,
|
||||
InputEndIconClassName,
|
||||
endIconClassName,
|
||||
)}
|
||||
data-has-onclick={!!endIconOnClick}
|
||||
name={endIcon}
|
||||
onClick={endIconOnClick}
|
||||
size={size}
|
||||
{...restOfEndIconProps}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</InputContainer>
|
||||
{description && (
|
||||
<Description
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ interface Props extends TextFieldProps {
|
|||
labelPosition?: "top" | "left";
|
||||
/** name */
|
||||
name?: string;
|
||||
/** postfix */
|
||||
postfix?: string;
|
||||
/** start icon */
|
||||
startIcon?: string;
|
||||
/** start icon props */
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = () => `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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
1
app/client/src/hooks/index.ts
Normal file
1
app/client/src/hooks/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./useIsCloudBillingEnabled";
|
||||
15
app/client/src/hooks/useIsCloudBillingEnabled.ts
Normal file
15
app/client/src/hooks/useIsCloudBillingEnabled.ts
Normal file
|
|
@ -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 };
|
||||
|
|
@ -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 = (
|
||||
<>
|
||||
<div className="px-2 flex align-center justify-center text-center text-[color:var(--ads-v2\-color-fg)] text-[14px]">
|
||||
{createMessage(ALREADY_HAVE_AN_ACCOUNT)}
|
||||
<Link
|
||||
className="t--sign-up t--signup-link"
|
||||
kind="primary"
|
||||
target="_self"
|
||||
to={AUTH_LOGIN_URL}
|
||||
>
|
||||
{createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)}
|
||||
</Link>
|
||||
</div>
|
||||
{isCloudBillingEnabled && isHostnameEqualtoLogin ? (
|
||||
<div className="px-2 flex flex-col items-center justify-center text-center text-[color:var(--ads-v2\-color-fg)] text-[14px]">
|
||||
{createMessage(ALREADY_USING_APPSMITH)}
|
||||
<Link
|
||||
className="t--sign-up t--signup-link"
|
||||
kind="primary"
|
||||
target="_self"
|
||||
to={ORG_LOGIN_PATH}
|
||||
>
|
||||
{createMessage(SIGN_IN_TO_AN_EXISTING_ORGANISATION)}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-2 flex align-center justify-center text-center text-[color:var(--ads-v2\-color-fg)] text-[14px]">
|
||||
{createMessage(ALREADY_HAVE_AN_ACCOUNT)}
|
||||
<Link
|
||||
className="t--sign-up t--signup-link"
|
||||
kind="primary"
|
||||
target="_self"
|
||||
to={AUTH_LOGIN_URL}
|
||||
>
|
||||
{createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{cloudHosting && (
|
||||
<>
|
||||
<OrWithLines>or</OrWithLines>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<form onSubmit={props.handleSubmit(onSubmit)}>
|
||||
<Space />
|
||||
{isCloudBillingEnabled && (
|
||||
<>
|
||||
<Space />
|
||||
<FormTextField
|
||||
data-testid="t--user-full-name"
|
||||
label={createMessage(WELCOME_FORM_FULL_NAME)}
|
||||
name="fullName"
|
||||
placeholder="Enter your full name"
|
||||
/>
|
||||
<Space />
|
||||
</>
|
||||
)}
|
||||
<Field
|
||||
component={RadioButtonGroup}
|
||||
label={createMessage(WELCOME_FORM_NON_SUPER_USER_PROFICIENCY_LEVEL)}
|
||||
|
|
|
|||
13
app/client/src/selectors/cloudBillingSelectors.tsx
Normal file
13
app/client/src/selectors/cloudBillingSelectors.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { createSelector } from "reselect";
|
||||
|
||||
import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors";
|
||||
|
||||
/**
|
||||
* Checks if the cloud billing is enabled via the license_multi_org_enabled feature flag
|
||||
*
|
||||
* @returns boolean
|
||||
*/
|
||||
export const getIsCloudBillingFeatureFlagEnabled = createSelector(
|
||||
selectFeatureFlags,
|
||||
(featureFlags) => featureFlags.license_multi_org_enabled,
|
||||
);
|
||||
5
app/client/src/utils/cloudBillingUtils.ts
Normal file
5
app/client/src/utils/cloudBillingUtils.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export function isLoginHostname(): boolean {
|
||||
const hostname = window.location.hostname;
|
||||
|
||||
return hostname.split(".")[0] === "login";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user