+ const [email, setEmail] = useState("");
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const { toast } = useToast();
+
+ const handleEmailSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!email.trim()) return;
+
+ trackEvent("waitlist_joined", { email: email, feature: "Ona" });
+
+ setIsSubmitted(true);
+
+ toast(
-
-
- Gitpod Classic
+ You're on the waitlist
+ We'll reach out to you soon.
+ ,
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Meet Ona - the privacy-first software engineering agent.
-
- Automated, standardized development environments hosted by us in Gitpod’s infrastructure. Users
- who joined before October 1, 2024 on non-Enterprise plans are considered Gitpod Classic users.
-
-
- Gitpod Classic is sunsetting fall 2025.{" "}
-
- Try the new Gitpod
- {" "}
- now (hosted compute coming soon).
-
+
+
+ Delegate software tasks to Ona. It writes code, runs tests, and opens a pull request. Or
+ jump in to inspect output or pair program in your IDE.
+
+
+ Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
+ support for any LLM.
+
+
+
+
-
);
diff --git a/components/dashboard/src/OnaRightPanel.tsx b/components/dashboard/src/OnaRightPanel.tsx
new file mode 100644
index 00000000000000..dab61ac0eb0958
--- /dev/null
+++ b/components/dashboard/src/OnaRightPanel.tsx
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2025 Gitpod GmbH. All rights reserved.
+ * Licensed under the GNU Affero General Public License (AGPL).
+ * See License.AGPL.txt in the project root for license information.
+ */
+
+import React, { useState, useEffect } from "react";
+import { trackEvent } from "./Analytics";
+import { useCurrentUser } from "./user-context";
+import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
+import { useToast } from "./components/toasts/Toasts";
+import onaWordmark from "./images/ona-wordmark.svg";
+import onaApplication from "./images/ona-application.webp";
+
+export const OnaRightPanel = () => {
+ const [email, setEmail] = useState("");
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const user = useCurrentUser();
+ const { toast } = useToast();
+
+ useEffect(() => {
+ const storedOnaData = localStorage.getItem("ona-waitlist-data");
+ if (storedOnaData) {
+ const { submitted } = JSON.parse(storedOnaData);
+ setIsSubmitted(submitted || false);
+ }
+ }, []);
+
+ const handleEmailSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!email.trim()) return;
+
+ const userEmail = user ? getPrimaryEmail(user) || email : email;
+ trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
+
+ setIsSubmitted(true);
+ localStorage.setItem("ona-waitlist-data", JSON.stringify({ submitted: true }));
+
+ toast(
+
+
You're on the waitlist
+
We'll reach out to you soon.
+
,
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ Meet Ona - the privacy-first software engineering agent.
+
+
+
+
+ Delegate software tasks to Ona. It writes code, runs tests, and opens a pull request. Or
+ jump in to inspect output or pair program in your IDE.
+
+
+ Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
+ support for any LLM.
+
+
+
+
+ {!isSubmitted ? (
+
+ ) : (
+
+ window.open("https://www.gitpod.io/solutions/ai", "_blank", "noopener,noreferrer")
+ }
+ className="w-full bg-white/20 backdrop-blur-sm text-white font-medium py-2.5 px-4 rounded-lg hover:bg-white/30 transition-colors border border-white/20 inline-flex items-center justify-center gap-2 text-sm"
+ >
+ Learn more
+ →
+
+ )}
+
+
+
+
+ );
+};
diff --git a/components/dashboard/src/images/ona-application.webp b/components/dashboard/src/images/ona-application.webp
new file mode 100644
index 00000000000000..a2ab9e8f6a9a6a
Binary files /dev/null and b/components/dashboard/src/images/ona-application.webp differ
diff --git a/components/dashboard/src/start/OnaBanner.tsx b/components/dashboard/src/start/OnaBanner.tsx
new file mode 100644
index 00000000000000..d4c0a373a63791
--- /dev/null
+++ b/components/dashboard/src/start/OnaBanner.tsx
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2024 Gitpod GmbH. All rights reserved.
+ * Licensed under the GNU Affero General Public License (AGPL).
+ * See License.AGPL.txt in the project root for license information.
+ */
+
+import React, { useEffect, useState } from "react";
+import { trackEvent } from "../Analytics";
+import { useCurrentUser } from "../user-context";
+import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
+import { useToast } from "../components/toasts/Toasts";
+import onaWordmark from "../images/ona-wordmark.svg";
+
+const onaBanner = {
+ type: "Introducing",
+ title: "ONA",
+ subtitle: "The privacy-first software engineering agent.",
+ ctaText: "Get early access",
+ learnMoreText: "Learn more",
+ link: "https://www.gitpod.io/solutions/ai",
+};
+
+interface OnaBannerProps {
+ compact?: boolean;
+}
+
+export const OnaBanner: React.FC
= ({ compact = false }) => {
+ const [onaClicked, setOnaClicked] = useState(false);
+ const [isDismissed, setIsDismissed] = useState(false);
+ const user = useCurrentUser();
+ const { toast } = useToast();
+
+ useEffect(() => {
+ const storedOnaData = localStorage.getItem("ona-banner-data");
+
+ if (storedOnaData) {
+ const { clicked, dismissed } = JSON.parse(storedOnaData);
+ setOnaClicked(clicked || false);
+ setIsDismissed(dismissed || false);
+ }
+ }, []);
+
+ const handleOnaBannerClick = () => {
+ // Track "Get early access" click
+ const userEmail = user ? getPrimaryEmail(user) || "" : "";
+ trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
+
+ setOnaClicked(true);
+ localStorage.setItem("ona-banner-data", JSON.stringify({ clicked: true, dismissed: isDismissed }));
+
+ // Show success toast
+ toast(
+
+
You're on the waitlist
+
We'll reach out to you soon.
+
,
+ );
+ };
+
+ const handleDismiss = () => {
+ setIsDismissed(true);
+ localStorage.setItem("ona-banner-data", JSON.stringify({ clicked: onaClicked, dismissed: true }));
+ };
+
+ // Don't render if dismissed
+ if (isDismissed) {
+ return null;
+ }
+
+ if (compact) {
+ return (
+
+ {/* Close button */}
+
+ ✕
+
+
+ {/* Compact layout */}
+
+ {onaBanner.type}
+
+
+
+
+ The privacy-first software engineering agent
+
+
+ {!onaClicked ? (
+
+ {onaBanner.ctaText}
+ →
+
+ ) : (
+
+ {onaBanner.learnMoreText}
+ →
+
+ )}
+
+ );
+ }
+
+ return (
+
+ {/* Close button */}
+
+ ✕
+
+
+ {/* Left section - ONA branding and image */}
+
+
+ {/* ONA Logo prominently displayed */}
+
+
+
+
+
+
+ {/* Right section - Text content and CTA */}
+
+
+ {/* Main title */}
+
+ The privacy-first software engineering agent
+
+
+ {/* CTA Button */}
+
+
+
+
+ );
+};
diff --git a/components/dashboard/src/start/StartPage.tsx b/components/dashboard/src/start/StartPage.tsx
index 5fcc96d4c9d877..309dc99d924385 100644
--- a/components/dashboard/src/start/StartPage.tsx
+++ b/components/dashboard/src/start/StartPage.tsx
@@ -15,6 +15,8 @@ import { useWorkspaceDefaultImageQuery } from "../data/workspaces/default-worksp
import { GetWorkspaceDefaultImageResponse_Source } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import { ProductLogo } from "../components/ProductLogo";
import { useIsDataOps } from "../data/featureflag-query";
+import { isGitpodIo } from "../utils";
+import { OnaBanner } from "./OnaBanner";
export enum StartPhase {
Checking = 0,
@@ -100,7 +102,14 @@ export function StartPage(props: StartPageProps) {
const isDataOps = useIsDataOps();
return (
-
+
+ {/* OnaBanner positioned on the side when workspace is running */}
+ {isGitpodIo() && (
+
+
+
+ )}
+
{
- const [showOnaBanner, setShowOnaBanner] = useState(true);
const [onaClicked, setOnaClicked] = useState(false);
+ const [isDismissed, setIsDismissed] = useState(false);
const user = useCurrentUser();
const { toast } = useToast();
useEffect(() => {
- const storedOnaData = localStorage.getItem("ona-banner-data");
+ const storedOnaData = localStorage.getItem("workspaces-ona-banner-data");
// Check Ona banner state
if (storedOnaData) {
- const { dismissed, clicked } = JSON.parse(storedOnaData);
- setShowOnaBanner(!dismissed);
+ const { clicked, dismissed } = JSON.parse(storedOnaData);
setOnaClicked(clicked || false);
+ setIsDismissed(dismissed || false);
}
// Clean up old blog banner data
@@ -47,7 +47,15 @@ export const OnaBanner: React.FC = () => {
trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
setOnaClicked(true);
- localStorage.setItem("ona-banner-data", JSON.stringify({ dismissed: false, clicked: true }));
+ localStorage.setItem(
+ "workspaces-ona-banner-data",
+ JSON.stringify({ clicked: true, dismissed: isDismissed }),
+ );
+
+ // Also set the global ona-banner-data clicked state (preserve existing dismissed state)
+ const existingOnaData = localStorage.getItem("ona-banner-data");
+ const existingDismissed = existingOnaData ? JSON.parse(existingOnaData).dismissed || false : false;
+ localStorage.setItem("ona-banner-data", JSON.stringify({ clicked: true, dismissed: existingDismissed }));
// Show success toast
toast(
@@ -62,48 +70,51 @@ export const OnaBanner: React.FC = () => {
}
};
- const handleOnaBannerDismiss = () => {
- setShowOnaBanner(false);
- localStorage.setItem("ona-banner-data", JSON.stringify({ dismissed: true, clicked: onaClicked }));
+ const handleDismiss = () => {
+ setIsDismissed(true);
+ localStorage.setItem("workspaces-ona-banner-data", JSON.stringify({ clicked: onaClicked, dismissed: true }));
};
+ // Don't render if dismissed
+ if (isDismissed) {
+ return null;
+ }
+
return (
- {showOnaBanner && (
-
+ {/* Close button */}
+
- {/* Close button */}
-
- ✕
-
-
- {/* Content */}
-
-
- {onaBanner.type}
-
-
-
{onaBanner.subtitle}
-
+ ✕
+
- {/* CTA Button */}
-
- {onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}
-
+ {/* Content */}
+
+
+ {onaBanner.type}
+
+
+
{onaBanner.subtitle}
- )}
+
+ {/* CTA Button */}
+
+ {onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}
+
+
);
};
diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx
index ec6703814770ca..94bc7508a41f6b 100644
--- a/components/dashboard/src/workspaces/Workspaces.tsx
+++ b/components/dashboard/src/workspaces/Workspaces.tsx
@@ -34,7 +34,7 @@ import { useUserLoader } from "../hooks/use-user-loader";
import { ReactComponent as GitpodStrokedSVG } from "../icons/gitpod-stroked.svg";
import { VideoSection } from "../onboarding/VideoSection";
import { OrganizationJoinModal } from "../teams/onboarding/OrganizationJoinModal";
-import { BlogBanners } from "./BlogBanners";
+// import { BlogBanners } from "./BlogBanners";
import { EmptyWorkspacesContent } from "./EmptyWorkspacesContent";
import PersonalizedContent from "./PersonalizedContent";
import { VideoCarousel } from "./VideoCarousel";
@@ -504,7 +504,8 @@ const WorkspacesPage: FunctionComponent = () => {
-
+ {/* Uncomment the following, if you need side banners in future */}
+ {/*
*/}
)}