diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index e45425ae9c..1830b12cdd 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -87,6 +87,8 @@ 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 USING_APPSMITH = () => `Using Appsmith?`;
+export const YOU_VE_ALREADY_SIGNED_INTO = () => `You've already signed into`;
export const SIGN_IN_TO_AN_EXISTING_ORGANISATION = () =>
`Sign in to an existing organisation`;
diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx
index da9669310c..e25b2a0301 100644
--- a/app/client/src/ce/pages/Applications/index.tsx
+++ b/app/client/src/ce/pages/Applications/index.tsx
@@ -140,6 +140,7 @@ import {
GitImportOverrideModal,
} from "git";
import OldRepoLimitExceededErrorModal from "pages/Editor/gitSync/RepoLimitExceededErrorModal";
+import { trackCurrentDomain } from "utils/multiOrgDomains";
function GitModals() {
const isGitModEnabled = useGitModEnabled();
@@ -1149,6 +1150,9 @@ export class Applications<
// Whenever we go back to home page from application page,
// we should reset current workspace, as this workspace is not in context anymore
this.props.resetCurrentWorkspace();
+
+ // Track the current domain for multi-org functionality
+ trackCurrentDomain();
}
componentWillUnmount() {
diff --git a/app/client/src/pages/UserAuth/SignUp.tsx b/app/client/src/pages/UserAuth/SignUp.tsx
index 17cd3f7844..c6ff0937c8 100644
--- a/app/client/src/pages/UserAuth/SignUp.tsx
+++ b/app/client/src/pages/UserAuth/SignUp.tsx
@@ -26,14 +26,14 @@ import {
GOOGLE_RECAPTCHA_KEY_ERROR,
LOOKING_TO_SELF_HOST,
VISIT_OUR_DOCS,
- ALREADY_USING_APPSMITH,
SIGN_IN_TO_AN_EXISTING_ORGANISATION,
- LOGIN_PAGE_TITLE,
+ USING_APPSMITH,
+ YOU_VE_ALREADY_SIGNED_INTO,
} from "ee/constants/messages";
import FormTextField from "components/utils/ReduxFormTextField";
import ThirdPartyAuth from "pages/UserAuth/ThirdPartyAuth";
import { FormGroup } from "@appsmith/ads-old";
-import { Button, Link, Callout } from "@appsmith/ads";
+import { Button, Link, Callout, Text } from "@appsmith/ads";
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
import type { SignupFormValues } from "pages/UserAuth/helpers";
@@ -66,6 +66,7 @@ import { isLoginHostname } from "utils/cloudBillingUtils";
import { appsmithTelemetry } from "instrumentation";
import { getIsAiAgentInstanceEnabled } from "ee/selectors/aiAgentSelectors";
import { getSafeErrorMessage } from "ee/constants/approvedErrorMessages";
+import { getRecentDomains, isValidAppsmithDomain } from "utils/multiOrgDomains";
declare global {
interface Window {
@@ -75,6 +76,7 @@ declare global {
}
}
const { cloudHosting, googleRecaptchaSiteKey } = getAppsmithConfigs();
+const recentDomains = getRecentDomains();
const validate = (values: SignupFormValues) => {
const errors: SignupFormValues = {};
@@ -94,6 +96,59 @@ const validate = (values: SignupFormValues) => {
return errors;
};
+const recentDomainsSection = recentDomains.length > 0 && (
+
+
+ {createMessage(YOU_VE_ALREADY_SIGNED_INTO)}
+
+
+
+ {recentDomains.map((domain, index) => {
+ const orgName = domain
+ .split(".")[0]
+ .split("-")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ");
+
+ const avatarLetter = String.fromCharCode(65 + (index % 26));
+
+ return (
+
+
+
+ {avatarLetter}
+
+
+
+ {orgName}
+
+
+ {domain}
+
+
+
+
+
+ );
+ })}
+
+
+);
+
type SignUpFormProps = InjectedFormProps<
SignupFormValues,
{ emailValue: string }
@@ -200,33 +255,34 @@ export function SignUp(props: SignUpFormProps) {
}
};
+ const cloudBillingSignIn = (
+
+ {createMessage(USING_APPSMITH)}
+
+ {createMessage(SIGN_IN_TO_AN_EXISTING_ORGANISATION)}
+
+
+ );
+
const footerSection = (
<>
- {isCloudBillingEnabled && isHostnameEqualtoLogin ? (
-
- {createMessage(ALREADY_USING_APPSMITH)}
-
- {createMessage(SIGN_IN_TO_AN_EXISTING_ORGANISATION)}
-
-
- ) : (
-
- {createMessage(ALREADY_HAVE_AN_ACCOUNT)}
-
- {createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)}
-
-
- )}
+
+ {createMessage(ALREADY_HAVE_AN_ACCOUNT)}
+
+ {createMessage(SIGNUP_PAGE_LOGIN_LINK_TEXT)}
+
+
+
{cloudHosting && !isAiAgentInstanceEnabled && (
<>
or
@@ -249,10 +305,8 @@ export function SignUp(props: SignUpFormProps) {
return (
{htmlPageTitle}
@@ -313,6 +367,8 @@ export function SignUp(props: SignUpFormProps) {
)}
+ {isCloudBillingEnabled && isHostnameEqualtoLogin && cloudBillingSignIn}
+ {isCloudBillingEnabled && isHostnameEqualtoLogin && recentDomainsSection}
);
}
diff --git a/app/client/src/utils/multiOrgDomains.ts b/app/client/src/utils/multiOrgDomains.ts
new file mode 100644
index 0000000000..aaf70057c9
--- /dev/null
+++ b/app/client/src/utils/multiOrgDomains.ts
@@ -0,0 +1,105 @@
+const COOKIE_NAME = "appsmith_recent_domains";
+const EXPIRY_DAYS = 30;
+const MAX_DOMAINS = 10;
+
+interface DomainEntry {
+ domain: string;
+ timestamp: number;
+}
+
+function getCurrentDomain(): string {
+ return window.location.hostname;
+}
+
+function isValidAppsmithDomain(domain: string): boolean {
+ return (
+ domain.endsWith(".appsmith.com") &&
+ !domain.startsWith("login.") &&
+ !domain.startsWith("release.") &&
+ !domain.startsWith("dev.") &&
+ /^[a-z0-9-]+\.appsmith\.com$/i.test(domain)
+ );
+}
+
+function isMultiOrgDomain(): boolean {
+ const hostname = getCurrentDomain();
+
+ return isValidAppsmithDomain(hostname);
+}
+
+function getStoredDomains(): DomainEntry[] {
+ const cookieValue = getCookie(COOKIE_NAME);
+
+ if (!cookieValue) return [];
+
+ try {
+ return JSON.parse(decodeURIComponent(cookieValue));
+ } catch (e) {
+ return [];
+ }
+}
+
+function storeDomains(domains: DomainEntry[]): void {
+ const expires = new Date();
+
+ expires.setDate(expires.getDate() + EXPIRY_DAYS);
+
+ const cookieValue = encodeURIComponent(JSON.stringify(domains));
+
+ document.cookie = `${COOKIE_NAME}=${cookieValue}; expires=${expires.toUTCString()}; domain=.appsmith.com; path=/; SameSite=Lax`;
+}
+
+function getCookie(name: string): string | null {
+ const value = `; ${document.cookie}`;
+ const parts = value.split(`; ${name}=`);
+
+ if (parts.length === 2) {
+ return parts.pop()?.split(";").shift() || null;
+ }
+
+ return null;
+}
+
+export function trackCurrentDomain(): void {
+ if (!isMultiOrgDomain()) {
+ return;
+ }
+
+ const currentDomain = getCurrentDomain();
+ const currentTime = Date.now();
+
+ let domains = getStoredDomains();
+
+ domains = domains.filter((entry) => entry.domain !== currentDomain);
+
+ domains.unshift({
+ domain: currentDomain,
+ timestamp: currentTime,
+ });
+
+ domains = domains.slice(0, MAX_DOMAINS);
+
+ const thirtyDaysAgo = currentTime - 30 * 24 * 60 * 60 * 1000;
+
+ domains = domains.filter((entry) => entry.timestamp > thirtyDaysAgo);
+
+ storeDomains(domains);
+}
+
+export function getRecentDomains(): string[] {
+ const domains = getStoredDomains();
+
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
+
+ return domains
+ .filter((entry) => entry.timestamp > thirtyDaysAgo)
+ .map((entry) => entry.domain)
+ .filter((domain) => domain !== getCurrentDomain())
+ .filter((domain) => isValidAppsmithDomain(domain));
+}
+
+export function clearRecentDomains(): void {
+ document.cookie = `${COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=.appsmith.com; path=/;`;
+}
+
+export { isValidAppsmithDomain };