+
+ {t(I18nKey.ORG$ACCOUNT)}
+
-
- {!shouldHideSelector && (
-
-
-
- )}
+
+ {!shouldHideSelector && (
+
+
+
+ )}
+
+ {!isMember && !isPersonalOrg && (
+
+
+
+ {t(I18nKey.ORG$INVITE_ORG_MEMBERS)}
+
+
+
+
+
+
+ {t(I18nKey.COMMON$ORGANIZATION)}
+
+
+
+ {t(I18nKey.ORG$ORGANIZATION_MEMBERS)}
+
+
+
+ )}
- {!isMember && !isPersonalOrg && (
-
-
- {t(I18nKey.ORG$INVITE_ORG_MEMBERS)}
-
-
-
-
-
-
- {t(I18nKey.COMMON$ORGANIZATION)}
-
-
-
- {t(I18nKey.ORG$ORGANIZATION_MEMBERS)}
-
-
+ {navItems.map((item) => (
+
+ {React.cloneElement(item.icon, {
+ className: "text-white",
+ width: 16,
+ height: 16,
+ } as React.SVGProps
)}
+ {t(item.text)}
+
+ ))}
- )}
-
- {navItems.map((item) => (
-
+
+
+
+ {t(I18nKey.SIDEBAR$DOCS)}
+
-
-
-
-
+
+ {showCta &&
}
+
);
}
diff --git a/frontend/src/hooks/use-tracking.ts b/frontend/src/hooks/use-tracking.ts
index 70fc98f810..a81aa46ccc 100644
--- a/frontend/src/hooks/use-tracking.ts
+++ b/frontend/src/hooks/use-tracking.ts
@@ -122,6 +122,13 @@ export const useTracking = () => {
});
};
+ const trackSaasSelfhostedInquiry = ({ location }: { location: string }) => {
+ posthog.capture("saas_selfhosted_inquiry", {
+ location,
+ ...commonProperties,
+ });
+ };
+
return {
trackLoginButtonClick,
trackConversationCreated,
@@ -134,5 +141,6 @@ export const useTracking = () => {
trackCreditLimitReached,
trackAddTeamMembersButtonClick,
trackOnboardingCompleted,
+ trackSaasSelfhostedInquiry,
};
};
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts
index 9d85c9528b..81b1301ace 100644
--- a/frontend/src/i18n/declaration.ts
+++ b/frontend/src/i18n/declaration.ts
@@ -1171,4 +1171,7 @@ export enum I18nKey {
DEVICE$CONTINUE = "DEVICE$CONTINUE",
DEVICE$AUTH_REQUIRED = "DEVICE$AUTH_REQUIRED",
DEVICE$SIGN_IN_PROMPT = "DEVICE$SIGN_IN_PROMPT",
+ CTA$ENTERPRISE_TITLE = "CTA$ENTERPRISE_TITLE",
+ CTA$ENTERPRISE_DESCRIPTION = "CTA$ENTERPRISE_DESCRIPTION",
+ CTA$LEARN_MORE = "CTA$LEARN_MORE",
}
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index e1cbb821c1..da7b4f391f 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -18734,5 +18734,53 @@
"es": "Por favor, inicia sesión para autorizar tu dispositivo.",
"tr": "Cihazınızı yetkilendirmek için lütfen giriş yapın.",
"uk": "Будь ласка, увійдіть, щоб авторизувати свій пристрій."
+ },
+ "CTA$ENTERPRISE_TITLE": {
+ "en": "Get OpenHands For Enterprise",
+ "ja": "エンタープライズ向けOpenHandsを入手",
+ "zh-CN": "获取企业版 OpenHands",
+ "zh-TW": "獲取企業版 OpenHands",
+ "ko-KR": "기업용 OpenHands 받기",
+ "no": "Få OpenHands for bedrift",
+ "it": "Ottieni OpenHands per Enterprise",
+ "pt": "Obtenha o OpenHands para Enterprise",
+ "es": "Obtén OpenHands para Empresas",
+ "ar": "احصل على OpenHands للمؤسسات",
+ "fr": "Obtenez OpenHands pour Entreprise",
+ "tr": "Kurumsal OpenHands'i Edinin",
+ "de": "OpenHands für Unternehmen",
+ "uk": "Отримайте OpenHands для підприємств"
+ },
+ "CTA$ENTERPRISE_DESCRIPTION": {
+ "en": "Cloud allows you to access OpenHands anywhere and coordinate with your team like never before.",
+ "ja": "クラウドを使用すると、どこからでもOpenHandsにアクセスし、チームとこれまでにない方法で連携できます。",
+ "zh-CN": "云端让您可以随时随地访问 OpenHands,并以前所未有的方式与团队协作。",
+ "zh-TW": "雲端讓您可以隨時隨地存取 OpenHands,並以前所未有的方式與團隊協作。",
+ "ko-KR": "클라우드를 통해 어디서나 OpenHands에 접속하고 팀과 이전과는 다른 방식으로 협업할 수 있습니다.",
+ "no": "Cloud lar deg få tilgang til OpenHands hvor som helst og koordinere med teamet ditt som aldri før.",
+ "it": "Cloud ti permette di accedere a OpenHands ovunque e coordinare con il tuo team come mai prima d'ora.",
+ "pt": "O Cloud permite que você acesse o OpenHands de qualquer lugar e coordene com sua equipe como nunca antes.",
+ "es": "Cloud le permite acceder a OpenHands desde cualquier lugar y coordinar con su equipo como nunca antes.",
+ "ar": "يتيح لك Cloud الوصول إلى OpenHands من أي مكان والتنسيق مع فريقك بشكل لم يسبق له مثيل.",
+ "fr": "Cloud vous permet d'accéder à OpenHands n'importe où et de coordonner avec votre équipe comme jamais auparavant.",
+ "tr": "Cloud, OpenHands'e her yerden erişmenizi ve ekibinizle daha önce hiç olmadığı gibi koordinasyon sağlamanızı mümkün kılar.",
+ "de": "Cloud ermöglicht Ihnen den Zugriff auf OpenHands von überall und die Koordination mit Ihrem Team wie nie zuvor.",
+ "uk": "Cloud дозволяє отримати доступ до OpenHands будь-де та координувати роботу з вашою командою як ніколи раніше."
+ },
+ "CTA$LEARN_MORE": {
+ "en": "Learn more",
+ "ja": "詳細を見る",
+ "zh-CN": "了解更多",
+ "zh-TW": "了解更多",
+ "ko-KR": "자세히 알아보기",
+ "no": "Lær mer",
+ "it": "Scopri di più",
+ "pt": "Saiba mais",
+ "es": "Más información",
+ "ar": "اعرف المزيد",
+ "fr": "En savoir plus",
+ "tr": "Daha fazla bilgi",
+ "de": "Mehr erfahren",
+ "uk": "Дізнатися більше"
}
}
diff --git a/frontend/src/icons/stacked.svg b/frontend/src/icons/stacked.svg
new file mode 100644
index 0000000000..3f15d38765
--- /dev/null
+++ b/frontend/src/icons/stacked.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/src/routes/device-verify.tsx b/frontend/src/routes/device-verify.tsx
index aabc94e544..b05db95240 100644
--- a/frontend/src/routes/device-verify.tsx
+++ b/frontend/src/routes/device-verify.tsx
@@ -5,7 +5,7 @@ import { useIsAuthed } from "#/hooks/query/use-is-authed";
import { EnterpriseBanner } from "#/components/features/device-verify/enterprise-banner";
import { I18nKey } from "#/i18n/declaration";
import { H1 } from "#/ui/typography";
-import { PROJ_USER_JOURNEY } from "#/utils/feature-flags";
+import { ENABLE_PROJ_USER_JOURNEY } from "#/utils/feature-flags";
export default function DeviceVerify() {
const { t } = useTranslation();
@@ -16,7 +16,7 @@ export default function DeviceVerify() {
messageKey: I18nKey;
} | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
- const showEnterpriseBanner = PROJ_USER_JOURNEY();
+ const showEnterpriseBanner = ENABLE_PROJ_USER_JOURNEY();
// Get user_code from URL parameters
const userCode = searchParams.get("user_code");
diff --git a/frontend/src/tailwind.css b/frontend/src/tailwind.css
index eee31d1d16..8cbacea7ef 100644
--- a/frontend/src/tailwind.css
+++ b/frontend/src/tailwind.css
@@ -376,3 +376,9 @@
animation: shine 2s linear infinite;
background: radial-gradient(circle at center, rgb(24 24 27 / 85%), transparent) -200% 50% / 200% 100% no-repeat, #f4f4f5;
}
+
+/* CTA card gradient and shadow */
+.cta-card-gradient {
+ background: radial-gradient(85.36% 123.38% at 50% 0%, rgba(255, 255, 255, 0.14) 0%, rgba(0, 0, 0, 0) 100%);
+ box-shadow: 0px 4px 6px -4px rgba(0, 0, 0, 0.1), 0px 10px 15px -3px rgba(0, 0, 0, 0.1);
+}
diff --git a/frontend/src/ui/card.tsx b/frontend/src/ui/card.tsx
index 519cbd5730..ea420c4296 100644
--- a/frontend/src/ui/card.tsx
+++ b/frontend/src/ui/card.tsx
@@ -2,43 +2,30 @@ import { ReactNode } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/utils/utils";
-const cardVariants = cva(
- "w-full flex flex-col rounded-[12px] p-[20px] border border-[#727987] bg-[#26282D] relative",
- {
- variants: {
- gap: {
- default: "gap-[10px]",
- large: "gap-6",
- },
- minHeight: {
- default: "min-h-[286px] md:min-h-auto",
- small: "min-h-[263.5px]",
- },
- },
- defaultVariants: {
- gap: "default",
- minHeight: "default",
+const cardVariants = cva("flex", {
+ variants: {
+ theme: {
+ default: "relative bg-[#26282D] border border-[#727987] rounded-xl",
+ outlined: "relative bg-transparent border border-[#727987] rounded-xl",
+ dark: "relative bg-black border border-[#242424] rounded-2xl",
},
},
-);
+ defaultVariants: {
+ theme: "default",
+ },
+});
interface CardProps extends VariantProps
{
- children: ReactNode;
+ children?: ReactNode;
className?: string;
testId?: string;
}
-export function Card({
- children,
- className = "",
- testId,
- gap,
- minHeight,
-}: CardProps) {
+export function Card({ children, className, testId, theme }: CardProps) {
return (
{children}
diff --git a/frontend/src/ui/context-menu.tsx b/frontend/src/ui/context-menu.tsx
index 1a6af5cbcd..4d31d629bb 100644
--- a/frontend/src/ui/context-menu.tsx
+++ b/frontend/src/ui/context-menu.tsx
@@ -2,42 +2,53 @@ import React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/utils/utils";
-const contextMenuVariants = cva(
- "absolute bg-tertiary rounded-[6px] text-white overflow-hidden z-50 context-menu-box-shadow",
- {
- variants: {
- size: {
- compact: "py-1 px-1",
- default: "py-[6px] px-1",
- },
- layout: {
- vertical: "flex flex-col gap-2",
- },
- position: {
- top: "bottom-full",
- bottom: "top-full",
- },
- spacing: {
- default: "mt-2",
- },
- alignment: {
- left: "left-0",
- right: "right-0",
- },
+const contextMenuVariants = cva("text-white overflow-hidden z-50", {
+ variants: {
+ theme: {
+ default:
+ "absolute bg-tertiary rounded-[6px] context-menu-box-shadow py-[6px] px-1",
+ naked: "relative",
},
- defaultVariants: {
- size: "default",
- layout: "vertical",
- spacing: "default",
+ size: {
+ compact: "py-1 px-1",
+ default: "",
+ },
+ layout: {
+ vertical: "flex flex-col gap-2",
+ },
+ position: {
+ top: "bottom-full",
+ bottom: "top-full",
+ },
+ spacing: {
+ default: "mt-2",
+ none: "",
+ },
+ alignment: {
+ left: "left-0",
+ right: "right-0",
},
},
-);
+ compoundVariants: [
+ {
+ theme: "naked",
+ className: "shadow-none",
+ },
+ ],
+ defaultVariants: {
+ theme: "default",
+ size: "default",
+ layout: "vertical",
+ spacing: "default",
+ },
+});
interface ContextMenuProps {
ref?: React.RefObject;
testId?: string;
children: React.ReactNode;
className?: React.HTMLAttributes["className"];
+ theme?: VariantProps["theme"];
size?: VariantProps["size"];
layout?: VariantProps["layout"];
position?: VariantProps["position"];
@@ -50,6 +61,7 @@ export function ContextMenu({
children,
className,
ref,
+ theme,
size,
layout,
position,
@@ -61,7 +73,14 @@ export function ContextMenu({
data-testid={testId}
ref={ref}
className={cn(
- contextMenuVariants({ size, layout, position, spacing, alignment }),
+ contextMenuVariants({
+ theme,
+ size,
+ layout,
+ position,
+ spacing,
+ alignment,
+ }),
className,
)}
>
diff --git a/frontend/src/ui/dropdown/dropdown-menu.tsx b/frontend/src/ui/dropdown/dropdown-menu.tsx
index 7880089566..80bf7081a2 100644
--- a/frontend/src/ui/dropdown/dropdown-menu.tsx
+++ b/frontend/src/ui/dropdown/dropdown-menu.tsx
@@ -27,7 +27,7 @@ export function DropdownMenu({
void;
testId?: string;
+ className?: string;
}
export function Dropdown({
@@ -30,6 +31,7 @@ export function Dropdown({
defaultValue,
onChange,
testId,
+ className,
}: DropdownProps) {
const [inputValue, setInputValue] = useState(defaultValue?.label ?? "");
const [searchTerm, setSearchTerm] = useState("");
@@ -98,6 +100,7 @@ export function Dropdown({
"bg-tertiary border border-[#717888] rounded w-full p-2",
"flex items-center gap-2",
isDisabled && "cursor-not-allowed opacity-60",
+ className,
)}
>
export const ENABLE_ONBOARDING = () => loadFeatureFlag("ENABLE_ONBOARDING");
export const ENABLE_SANDBOX_GROUPING = () =>
loadFeatureFlag("SANDBOX_GROUPING");
-export const PROJ_USER_JOURNEY = () => loadFeatureFlag("PROJ_USER_JOURNEY");
+export const ENABLE_PROJ_USER_JOURNEY = () =>
+ loadFeatureFlag("PROJ_USER_JOURNEY");