Skip to content

Commit 1057aaf

Browse files
Add Ona banner to start page with compact and full versions
1 parent 2946c9a commit 1057aaf

File tree

4 files changed

+204
-43
lines changed

4 files changed

+204
-43
lines changed
92.5 KB
Loading
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* Copyright (c) 2024 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import React, { useEffect, useState } from "react";
8+
import { trackEvent } from "../Analytics";
9+
import { useCurrentUser } from "../user-context";
10+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
11+
import { useToast } from "../components/toasts/Toasts";
12+
import onaWordmark from "../images/ona-wordmark.svg";
13+
import onaApplication from "../images/ona-application.webp";
14+
15+
const onaBanner = {
16+
type: "Introducing",
17+
title: "ONA",
18+
subtitle: "The privacy-first software engineering agent.",
19+
ctaText: "Get early access",
20+
learnMoreText: "Learn more",
21+
link: "https://ona.com/",
22+
};
23+
24+
interface OnaBannerProps {
25+
compact?: boolean;
26+
}
27+
28+
export const OnaBanner: React.FC<OnaBannerProps> = ({ compact = false }) => {
29+
const [onaClicked, setOnaClicked] = useState(false);
30+
const user = useCurrentUser();
31+
const { toast } = useToast();
32+
33+
useEffect(() => {
34+
const storedOnaData = localStorage.getItem("ona-banner-data");
35+
36+
if (storedOnaData) {
37+
const { clicked } = JSON.parse(storedOnaData);
38+
setOnaClicked(clicked || false);
39+
}
40+
}, []);
41+
42+
const handleOnaBannerClick = () => {
43+
if (!onaClicked) {
44+
// Track "Get early access" click
45+
const userEmail = user ? getPrimaryEmail(user) || "" : "";
46+
trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
47+
48+
setOnaClicked(true);
49+
localStorage.setItem("ona-banner-data", JSON.stringify({ clicked: true }));
50+
51+
// Show success toast
52+
toast(
53+
<div>
54+
<div className="font-medium">You're on the waitlist</div>
55+
<div className="text-sm opacity-80">We'll reach out to you soon.</div>
56+
</div>,
57+
);
58+
} else {
59+
// "Learn more" click - open link
60+
window.open(onaBanner.link, "_blank", "noopener,noreferrer");
61+
}
62+
};
63+
64+
if (compact) {
65+
return (
66+
<div
67+
className="relative rounded-lg hidden lg:flex flex-col gap-3 text-white max-w-80 p-4 shadow-lg"
68+
style={{
69+
background:
70+
"linear-gradient(340deg, #1F1329 0%, #333A75 20%, #556CA8 40%, #90A898 60%, #E2B15C 80%, #BEA462 100%)",
71+
}}
72+
>
73+
{/* Compact layout */}
74+
<div className="flex items-center gap-2 text-sm font-normal">
75+
{onaBanner.type}
76+
<img src={onaWordmark} alt="ONA" className="w-12" draggable="false" />
77+
</div>
78+
79+
<h3 className="text-white text-lg font-bold leading-tight">
80+
The privacy-first software engineering agent
81+
</h3>
82+
83+
<p className="text-white/90 text-sm leading-relaxed">
84+
Delegate software tasks to Ona. It writes code, runs tests, and opens a pull request. Or jump in to
85+
inspect output or pair program in your IDE.
86+
<br />
87+
<br />
88+
Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and support
89+
for any LLM.
90+
</p>
91+
92+
<button
93+
onClick={handleOnaBannerClick}
94+
className="bg-white/20 backdrop-blur-sm text-white font-medium py-1.5 px-4 rounded-full hover:bg-white/30 transition-colors border border-white/20 inline-flex items-center gap-2 text-sm w-fit"
95+
>
96+
{onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}
97+
<span className="font-bold"></span>
98+
</button>
99+
</div>
100+
);
101+
}
102+
103+
return (
104+
<div
105+
className="relative rounded-lg flex flex-col lg:flex-row gap-4 lg:gap-6 text-white max-w-5xl mx-auto p-4 lg:p-6 mt-4 mb-16"
106+
style={{
107+
background:
108+
"linear-gradient(340deg, #1F1329 0%, #333A75 20%, #556CA8 40%, #90A898 60%, #E2B15C 80%, #BEA462 100%)",
109+
}}
110+
>
111+
{/* Left section - ONA branding and image */}
112+
<div className="flex-1 max-w-full lg:max-w-[400px] order-2 lg:order-1">
113+
<div className="relative bg-white/10 backdrop-blur-sm rounded-lg pt-4 px-4 lg:px-6 lg:pt-6 ">
114+
{/* ONA Logo prominently displayed */}
115+
<div className="flex justify-center -mb-4 lg:-mb-6">
116+
<img src={onaWordmark} alt="ONA" className="w-28 lg:w-56" draggable="false" />
117+
</div>
118+
119+
{/* Application screenshot */}
120+
<div className="relative overflow-hidden">
121+
<img
122+
src={onaApplication}
123+
alt="Ona application preview"
124+
className="w-full translate-y-16 h-auto rounded-lg shadow-lg"
125+
draggable="false"
126+
/>
127+
</div>
128+
</div>
129+
</div>
130+
131+
{/* Right section - Text content and CTA */}
132+
<div className="flex-1 max-w-[500px] lg:max-w-[550px] order-1 lg:order-2 lg:ml-8 text-left">
133+
<div className="max-lg:mt-2 space-y-3 lg:space-y-4">
134+
{/* Main title */}
135+
<h2 className="text-white text-xl sm:text-2xl lg:text-3xl font-bold leading-tight">
136+
The privacy-first software engineering agent
137+
</h2>
138+
139+
{/* Description */}
140+
<div className="space-y-2 lg:space-y-3">
141+
<p className="text-[#FFFFFF99] text-sm sm:text-base lg:text-lg leading-relaxed">
142+
Delegate software tasks to Ona. It writes code, runs tests, and opens a pull request. Or
143+
jump in to inspect output or pair program in your IDE.
144+
</p>
145+
<p className="text-[#FFFFFF99] text-sm sm:text-base lg:text-lg leading-relaxed">
146+
Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
147+
support for any LLM.
148+
</p>
149+
</div>
150+
151+
{/* CTA Button */}
152+
<div className="pt-2 lg:pt-3 mb-4">
153+
<button
154+
onClick={handleOnaBannerClick}
155+
className="inline-flex items-center justify-center gap-2 bg-[#fdfdfd] text-[#12100C] px-4 lg:px-6 py-2 rounded-[14px] text-sm lg:text-base transition-all duration-200 shadow-sm h-10 lg:h-12 hover:shadow-md focus:shadow-md hover:bg-[#F5F4F3] font-medium"
156+
>
157+
<span>{onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}</span>
158+
<span className="font-bold"></span>
159+
</button>
160+
</div>
161+
</div>
162+
</div>
163+
</div>
164+
);
165+
};

components/dashboard/src/start/StartPage.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { useWorkspaceDefaultImageQuery } from "../data/workspaces/default-worksp
1515
import { GetWorkspaceDefaultImageResponse_Source } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
1616
import { ProductLogo } from "../components/ProductLogo";
1717
import { useIsDataOps } from "../data/featureflag-query";
18+
import { isGitpodIo } from "../utils";
19+
import { OnaBanner } from "./OnaBanner";
1820

1921
export enum StartPhase {
2022
Checking = 0,
@@ -99,8 +101,18 @@ export function StartPage(props: StartPageProps) {
99101
useDocumentTitle("Starting");
100102
const isDataOps = useIsDataOps();
101103

104+
// Check if workspace is running/ready - position OnaBanner differently
105+
const isWorkspaceRunning = phase === StartPhase.IdeReady || phase === StartPhase.Running;
106+
102107
return (
103-
<div className="w-screen h-screen align-middle">
108+
<div className="w-screen h-screen align-middle relative">
109+
{/* OnaBanner positioned on the side when workspace is running */}
110+
{isWorkspaceRunning && isGitpodIo() && (
111+
<div className="fixed bottom-4 right-4 z-1 max-w-sm">
112+
<OnaBanner compact={true} />
113+
</div>
114+
)}
115+
104116
<div className="flex flex-col mx-auto items-center text-center h-screen">
105117
<div className="h-1/3"></div>
106118
<ProductLogo
@@ -122,6 +134,8 @@ export function StartPage(props: StartPageProps) {
122134
showLatestIdeWarning={props.showLatestIdeWarning}
123135
error={props.error}
124136
/>
137+
{/* OnaBanner positioned in main flow when workspace is not running */}
138+
{!isWorkspaceRunning && isGitpodIo() && <OnaBanner />}
125139
</div>
126140
</div>
127141
);

components/dashboard/src/workspaces/BlogBanners.tsx

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const onaBanner = {
2121
};
2222

2323
export const OnaBanner: React.FC = () => {
24-
const [showOnaBanner, setShowOnaBanner] = useState(true);
2524
const [onaClicked, setOnaClicked] = useState(false);
2625
const user = useCurrentUser();
2726
const { toast } = useToast();
@@ -31,8 +30,7 @@ export const OnaBanner: React.FC = () => {
3130

3231
// Check Ona banner state
3332
if (storedOnaData) {
34-
const { dismissed, clicked } = JSON.parse(storedOnaData);
35-
setShowOnaBanner(!dismissed);
33+
const { clicked } = JSON.parse(storedOnaData);
3634
setOnaClicked(clicked || false);
3735
}
3836

@@ -47,7 +45,7 @@ export const OnaBanner: React.FC = () => {
4745
trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
4846

4947
setOnaClicked(true);
50-
localStorage.setItem("ona-banner-data", JSON.stringify({ dismissed: false, clicked: true }));
48+
localStorage.setItem("ona-banner-data", JSON.stringify({ clicked: true }));
5149

5250
// Show success toast
5351
toast(
@@ -62,48 +60,32 @@ export const OnaBanner: React.FC = () => {
6260
}
6361
};
6462

65-
const handleOnaBannerDismiss = () => {
66-
setShowOnaBanner(false);
67-
localStorage.setItem("ona-banner-data", JSON.stringify({ dismissed: true, clicked: onaClicked }));
68-
};
69-
7063
return (
7164
<div className="flex flex-col gap-4">
72-
{showOnaBanner && (
73-
<div
74-
className="relative rounded-lg overflow-hidden flex flex-col gap-4 text-white max-w-[320px] p-6"
75-
style={{
76-
background:
77-
"linear-gradient(340deg, #1F1329 0%, #333A75 20%, #556CA8 40%, #90A898 60%, #E2B15C 80%, #BEA462 100%)",
78-
}}
79-
>
80-
{/* Close button */}
81-
<button
82-
onClick={handleOnaBannerDismiss}
83-
className="absolute top-4 right-4 text-white/70 hover:text-white w-6 h-6 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
84-
aria-label="Dismiss banner"
85-
>
86-
87-
</button>
88-
89-
{/* Content */}
90-
<div className="flex flex-col gap-4">
91-
<div className="flex items-center gap-2 text-lg font-normal">
92-
{onaBanner.type}
93-
<img src={onaWordmark} alt="ONA" className="w-16" draggable="false" />
94-
</div>
95-
<div className="text-base font-normal opacity-90">{onaBanner.subtitle}</div>
65+
<div
66+
className="relative rounded-lg overflow-hidden flex flex-col gap-4 text-white max-w-[320px] p-6"
67+
style={{
68+
background:
69+
"linear-gradient(340deg, #1F1329 0%, #333A75 20%, #556CA8 40%, #90A898 60%, #E2B15C 80%, #BEA462 100%)",
70+
}}
71+
>
72+
{/* Content */}
73+
<div className="flex flex-col gap-4">
74+
<div className="flex items-center gap-2 text-lg font-normal">
75+
{onaBanner.type}
76+
<img src={onaWordmark} alt="ONA" className="w-16" draggable="false" />
9677
</div>
97-
98-
{/* CTA Button */}
99-
<button
100-
onClick={handleOnaBannerClick}
101-
className="bg-white/20 backdrop-blur-sm text-white font-medium py-1 px-6 rounded-full hover:bg-white/30 transition-colors border border-white/20 max-w-[180px]"
102-
>
103-
{onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}
104-
</button>
78+
<div className="text-base font-normal opacity-90">{onaBanner.subtitle}</div>
10579
</div>
106-
)}
80+
81+
{/* CTA Button */}
82+
<button
83+
onClick={handleOnaBannerClick}
84+
className="bg-white/20 backdrop-blur-sm text-white font-medium py-1 px-6 rounded-full hover:bg-white/30 transition-colors border border-white/20 max-w-[180px]"
85+
>
86+
{onaClicked ? onaBanner.learnMoreText : onaBanner.ctaText}
87+
</button>
88+
</div>
10789
</div>
10890
);
10991
};

0 commit comments

Comments
 (0)