diff --git a/components/dashboard/src/Analytics.tsx b/components/dashboard/src/Analytics.tsx
index ae3a90ca6c5bf9..114a3967f467c3 100644
--- a/components/dashboard/src/Analytics.tsx
+++ b/components/dashboard/src/Analytics.tsx
@@ -23,7 +23,8 @@ export type Event =
| "ide_configuration_changed"
| "status_rendered"
| "error_rendered"
- | "video_clicked";
+ | "video_clicked"
+ | "waitlist_joined";
type InternalEvent = Event | "path_changed" | "dashboard_clicked";
export type EventProperties =
@@ -38,7 +39,8 @@ export type EventProperties =
| TrackWorkspaceClassChanged
| TrackStatusRendered
| TrackErrorRendered
- | TrackVideoClicked;
+ | TrackVideoClicked
+ | TrackWaitlistJoined;
type InternalEventProperties = EventProperties | TrackDashboardClick | TrackPathChanged;
export interface TrackErrorRendered {
@@ -125,6 +127,11 @@ interface TrackPathChanged {
path: string;
}
+export interface TrackWaitlistJoined {
+ email: string;
+ feature: string;
+}
+
interface Traits {
unsubscribed_onboarding?: boolean;
unsubscribed_changelog?: boolean;
@@ -148,6 +155,7 @@ export function trackEvent(event: "ide_configuration_changed", properties: Track
export function trackEvent(event: "status_rendered", properties: TrackStatusRendered): void;
export function trackEvent(event: "error_rendered", properties: TrackErrorRendered): void;
export function trackEvent(event: "video_clicked", properties: TrackVideoClicked): void;
+export function trackEvent(event: "waitlist_joined", properties: TrackWaitlistJoined): void;
export function trackEvent(event: Event, properties: EventProperties): void {
trackEventInternal(event, properties);
}
diff --git a/components/dashboard/src/images/ona-wordmark.svg b/components/dashboard/src/images/ona-wordmark.svg
new file mode 100644
index 00000000000000..97a315a97bb895
--- /dev/null
+++ b/components/dashboard/src/images/ona-wordmark.svg
@@ -0,0 +1,5 @@
+
diff --git a/components/dashboard/src/workspaces/BlogBanners.tsx b/components/dashboard/src/workspaces/BlogBanners.tsx
index 309c0538105ec8..810a084e352622 100644
--- a/components/dashboard/src/workspaces/BlogBanners.tsx
+++ b/components/dashboard/src/workspaces/BlogBanners.tsx
@@ -5,96 +5,108 @@
*/
import React, { useEffect, useState } from "react";
-import blogBannerBg from "../images/blog-banner-bg.png";
+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 banners = [
- {
- type: "Watch recording",
- title: "Beyond Kubernetes: A deep-dive into Gitpod Flex with our CTO",
- link: "https://www.gitpod.io/events#watch-on-demand",
- },
- {
- type: "Blog Post",
- title: "Gitpod Enterprise:
Self-hosted, not self-managed",
- link: "https://www.gitpod.io/blog/self-hosted-not-self-managed",
- },
- {
- type: "Customer Story",
- title: "Thousands of hours spent on VM-based development environments reduced to zero using Gitpod",
- link: "https://www.gitpod.io/customers/kingland",
- },
- {
- type: "Gartner Report",
- title: `"By 2026, 60% of cloud workloads will be built and deployed using CDE's"`,
- link: "https://www.gitpod.io/blog/gartner-2023-cde-hypecycle",
- },
-];
-
-const initialBannerIndex = 0; // Index for "Self-hosted, not self-managed"
+const onaBanner = {
+ type: "Introducing",
+ title: "ONA",
+ subtitle: "The privacy-first software engineering agent.",
+ ctaText: "Get early access",
+ learnMoreText: "Learn more",
+ link: "https://ona.com/",
+};
-export const BlogBanners: React.FC = () => {
- const [currentBannerIndex, setCurrentBannerIndex] = useState(initialBannerIndex);
+export const OnaBanner: React.FC = () => {
+ const [showOnaBanner, setShowOnaBanner] = useState(true);
+ const [onaClicked, setOnaClicked] = useState(false);
+ const user = useCurrentUser();
+ const { toast } = useToast();
useEffect(() => {
- const storedBannerData = localStorage.getItem("blog-banner-data");
- const currentTime = new Date().getTime();
+ const storedOnaData = localStorage.getItem("ona-banner-data");
- if (storedBannerData) {
- const { lastIndex, lastTime } = JSON.parse(storedBannerData);
+ // Check Ona banner state
+ if (storedOnaData) {
+ const { dismissed, clicked } = JSON.parse(storedOnaData);
+ setShowOnaBanner(!dismissed);
+ setOnaClicked(clicked || false);
+ }
- if (currentTime - lastTime >= 2 * 24 * 60 * 60 * 1000) {
- // 2 days in milliseconds
- const nextIndex = getRandomBannerIndex(lastIndex);
- setCurrentBannerIndex(nextIndex);
- localStorage.setItem(
- "blog-banner-data",
- JSON.stringify({ lastIndex: nextIndex, lastTime: currentTime }),
- );
- } else {
- setCurrentBannerIndex(lastIndex);
- }
- } else {
- setCurrentBannerIndex(initialBannerIndex);
- localStorage.setItem(
- "blog-banner-data",
- JSON.stringify({ lastIndex: initialBannerIndex, lastTime: currentTime }),
+ // Clean up old blog banner data
+ localStorage.removeItem("blog-banner-data");
+ }, []);
+
+ const handleOnaBannerClick = () => {
+ if (!onaClicked) {
+ // 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({ dismissed: false, clicked: true }));
+
+ // Show success toast
+ toast(
+