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( +
+
You're on the waitlist
+
We'll reach out to you soon.
+
, ); + } else { + // "Learn more" click - open link + window.open(onaBanner.link, "_blank", "noopener,noreferrer"); } - }, []); + }; - const getRandomBannerIndex = (excludeIndex: number) => { - let nextIndex; - do { - nextIndex = Math.floor(Math.random() * banners.length); - } while (nextIndex === excludeIndex || nextIndex === initialBannerIndex); - return nextIndex; + const handleOnaBannerDismiss = () => { + setShowOnaBanner(false); + localStorage.setItem("ona-banner-data", JSON.stringify({ dismissed: true, clicked: onaClicked })); }; return ( -
- -
-
- {banners[currentBannerIndex].type} +
+ {showOnaBanner && ( +
+ {/* Close button */} + + + {/* Content */} +
+
+ {onaBanner.type} + ONA +
+
{onaBanner.subtitle}
-
+ + {/* CTA Button */} +
-
+ )}
); }; + +// Export with old name for backward compatibility +export const BlogBanners = OnaBanner;