Skip to content

Commit a0cf09d

Browse files
Redesign login page with Ona banner & Banners in Workspace start pages (#20909)
* Add Ona banner to start page with compact and full versions * Login page - Ona right panel for waitlist sign-up and information for gitpod.io users only * only send email what user submitted * Refactor login component to use installation configuration for enterprise detection and update learn more button to a link * improve login page * Update Ona banner links to point to Gitpod solutions and add dismiss functionality * improve login page * nice keyB * addressing review nit * Refactor OnaBanner and AppNotifications components for improved layout and dismiss functionality * Update OnaBanner positioning logic and adjust text alignment for improved layout * minor improvements
1 parent 2946c9a commit a0cf09d

File tree

8 files changed

+565
-84
lines changed

8 files changed

+565
-84
lines changed

components/dashboard/src/AppNotifications.tsx

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { useOrgBillingMode } from "./data/billing-mode/org-billing-mode-query";
2020
import { Organization } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
2121
import { MaintenanceModeBanner } from "./org-admin/MaintenanceModeBanner";
2222
import { MaintenanceNotificationBanner } from "./org-admin/MaintenanceNotificationBanner";
23+
import { useToast } from "./components/toasts/Toasts";
24+
import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";
2325
import onaWordmark from "./images/ona-wordmark.svg";
2426

2527
const KEY_APP_DISMISSED_NOTIFICATIONS = "gitpod-app-notifications-dismissed";
@@ -129,37 +131,84 @@ const INVALID_BILLING_ADDRESS = (stripePortalUrl: string | undefined) => {
129131
} as Notification;
130132
};
131133

132-
const GITPOD_CLASSIC_SUNSET = {
133-
id: "gitpod-classic-sunset",
134-
type: "info" as AlertType,
135-
preventDismiss: true, // This makes it so users can't dismiss the notification
136-
message: (
137-
<span className="text-md text-white font-semibold items-center justify-center">
138-
Meet <img src={onaWordmark} alt="Ona" className="inline align-middle w-12 mb-0.5" draggable="false" /> | the
139-
privacy-first software engineering agent |{" "}
140-
<a href="https://ona.com/" target="_blank" rel="noreferrer" className="underline hover:no-underline">
141-
Get early access
142-
</a>
143-
</span>
144-
),
145-
} as Notification;
134+
const GITPOD_CLASSIC_SUNSET = (
135+
user: User | undefined,
136+
toast: any,
137+
onaClicked: boolean,
138+
handleOnaBannerClick: () => void,
139+
) => {
140+
return {
141+
id: "gitpod-classic-sunset",
142+
type: "info" as AlertType,
143+
message: (
144+
<span className="text-md text-white font-semibold items-center justify-center">
145+
Meet <img src={onaWordmark} alt="Ona" className="inline align-middle w-12 mb-0.5" draggable="false" /> |
146+
the privacy-first software engineering agent |{" "}
147+
{!onaClicked ? (
148+
<button
149+
onClick={handleOnaBannerClick}
150+
className="underline hover:no-underline cursor-pointer bg-transparent border-none text-white font-semibold"
151+
>
152+
Get early access
153+
</button>
154+
) : (
155+
<a
156+
href="https://www.gitpod.io/solutions/ai"
157+
target="_blank"
158+
rel="noreferrer"
159+
className="underline hover:no-underline"
160+
>
161+
Learn more
162+
</a>
163+
)}
164+
</span>
165+
),
166+
} as Notification;
167+
};
146168

147169
export function AppNotifications() {
148170
const [topNotification, setTopNotification] = useState<Notification | undefined>(undefined);
171+
const [onaClicked, setOnaClicked] = useState(false);
149172
const { user, loading } = useUserLoader();
150173
const { mutateAsync } = useUpdateCurrentUserMutation();
174+
const { toast } = useToast();
151175

152176
const currentOrg = useCurrentOrg().data;
153177
const { data: billingMode } = useOrgBillingMode();
154178

179+
useEffect(() => {
180+
const storedOnaData = localStorage.getItem("ona-banner-data");
181+
if (storedOnaData) {
182+
const { clicked } = JSON.parse(storedOnaData);
183+
setOnaClicked(clicked || false);
184+
}
185+
}, []);
186+
187+
const handleOnaBannerClick = useCallback(() => {
188+
const userEmail = user ? getPrimaryEmail(user) || "" : "";
189+
trackEvent("waitlist_joined", { email: userEmail, feature: "Ona" });
190+
191+
setOnaClicked(true);
192+
const existingData = localStorage.getItem("ona-banner-data");
193+
const parsedData = existingData ? JSON.parse(existingData) : {};
194+
localStorage.setItem("ona-banner-data", JSON.stringify({ ...parsedData, clicked: true }));
195+
196+
toast(
197+
<div>
198+
<div className="font-medium">You're on the waitlist</div>
199+
<div className="text-sm opacity-80">We'll reach out to you soon.</div>
200+
</div>,
201+
);
202+
}, [user, toast]);
203+
155204
useEffect(() => {
156205
let ignore = false;
157206

158207
const updateNotifications = async () => {
159208
const notifications = [];
160209
if (!loading) {
161210
if (isGitpodIo()) {
162-
notifications.push(GITPOD_CLASSIC_SUNSET);
211+
notifications.push(GITPOD_CLASSIC_SUNSET(user, toast, onaClicked, handleOnaBannerClick));
163212
}
164213

165214
if (
@@ -193,7 +242,7 @@ export function AppNotifications() {
193242
return () => {
194243
ignore = true;
195244
};
196-
}, [loading, mutateAsync, user, currentOrg, billingMode]);
245+
}, [loading, mutateAsync, user, currentOrg, billingMode, onaClicked, handleOnaBannerClick, toast]);
197246

198247
const dismissNotification = useCallback(() => {
199248
if (!topNotification) {
@@ -213,7 +262,7 @@ export function AppNotifications() {
213262
{topNotification && (
214263
<Alert
215264
type={topNotification.type}
216-
closable={topNotification.id !== "gitpod-classic-sunset"} // Only show close button if it's not the sunset notification
265+
closable={true}
217266
onClose={() => {
218267
if (!topNotification.preventDismiss) {
219268
dismissNotification();

components/dashboard/src/Login.tsx

Lines changed: 132 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@ import { SSOLoginForm } from "./login/SSOLoginForm";
1717
import { useAuthProviderDescriptions } from "./data/auth-providers/auth-provider-descriptions-query";
1818
import { SetupPending } from "./login/SetupPending";
1919
import { useNeedsSetup } from "./dedicated-setup/use-needs-setup";
20+
import { useInstallationConfiguration } from "./data/installation/installation-config-query";
2021
import { AuthProviderDescription } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
2122
import { Button, ButtonProps } from "@podkit/buttons/Button";
2223
import { cn } from "@podkit/lib/cn";
2324
import { userClient } from "./service/public-api";
2425
import { ProductLogo } from "./components/ProductLogo";
2526
import { useIsDataOps } from "./data/featureflag-query";
26-
import GitpodClassicCard from "./images/gitpod-classic-card.png";
2727
import { LoadingState } from "@podkit/loading/LoadingState";
2828
import { isGitpodIo } from "./utils";
29+
import { trackEvent } from "./Analytics";
30+
import { useToast } from "./components/toasts/Toasts";
31+
import onaWordmark from "./images/ona-wordmark.svg";
32+
import onaApplication from "./images/ona-application.webp";
2933

3034
export function markLoggedIn() {
3135
document.cookie = GitpodCookie.generateCookie(window.location.hostname);
@@ -64,7 +68,8 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
6468
const [hostFromContext, setHostFromContext] = useState<string | undefined>();
6569
const [repoPathname, setRepoPathname] = useState<string | undefined>();
6670

67-
const enterprise = !!authProviders.data && authProviders.data.length === 0;
71+
const { data: installationConfig } = useInstallationConfiguration();
72+
const enterprise = !!installationConfig?.isDedicatedInstallation;
6873

6974
useEffect(() => {
7075
try {
@@ -93,9 +98,15 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
9398
return (
9499
<div
95100
id="login-container"
96-
className={cn("z-50 flex flex-col-reverse lg:flex-row w-full min-h-screen", {
97-
"bg-[#FDF1E7] dark:bg-[#23211e]": !enterprise,
98-
})}
101+
className="z-50 flex flex-col-reverse lg:flex-row w-full min-h-screen"
102+
style={
103+
!enterprise
104+
? {
105+
background:
106+
"linear-gradient(390deg, #1F1329 0%, #333A75 20%, #556CA8 50%, #90A898 60%, #90A898 70%, #E2B15C 90%, #BEA462 100%)",
107+
}
108+
: undefined
109+
}
99110
>
100111
{enterprise ? (
101112
<EnterpriseLoginWrapper
@@ -147,7 +158,7 @@ const PAYGLoginWrapper: FC<LoginWrapperProps> = ({ providerFromContext, repoPath
147158
<div
148159
id="login-section"
149160
// for some reason, min-h-dvh does not work, so we need tailwind's arbitrary values
150-
className="w-full min-h-[100dvh] lg:w-2/3 flex flex-col justify-center items-center bg-[#FDF1E7] dark:bg-[#23211e] p-2"
161+
className="w-full min-h-[100dvh] lg:w-full flex flex-col justify-center items-center p-2"
151162
>
152163
<div
153164
id="login-section-column"
@@ -212,6 +223,9 @@ const LoginContent = ({
212223
const authProviders = useAuthProviderDescriptions();
213224
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
214225

226+
const { data: installationConfig } = useInstallationConfiguration();
227+
const enterprise = !!installationConfig?.isDedicatedInstallation;
228+
215229
const updateUser = useCallback(async () => {
216230
await getGitpodService().reconnect();
217231
const { user } = await userClient.getAuthenticatedUser({});
@@ -314,32 +328,127 @@ const LoginContent = ({
314328
<SSOLoginForm onSuccess={authorizeSuccessful} />
315329
</div>
316330
{errorMessage && <ErrorMessage imgSrc={exclamation} message={errorMessage} />}
331+
332+
{/* Gitpod Classic sunset notice - only show for non-enterprise */}
333+
{!enterprise && (
334+
<div className="mt-6 text-center text-sm">
335+
<p className="text-pk-content-primary">
336+
Gitpod classic is sunsetting fall 2025.{" "}
337+
<a
338+
href="https://app.gitpod.io"
339+
target="_blank"
340+
rel="noopener noreferrer"
341+
className="gp-link hover:text-gray-600"
342+
>
343+
Try the new Gitpod
344+
</a>{" "}
345+
now (hosted compute & SWE agents coming soon)
346+
</p>
347+
</div>
348+
)}
317349
</div>
318350
);
319351
};
320352

321353
const RightProductDescriptionPanel = () => {
322-
return (
323-
<div className="w-full lg:w-1/3 flex flex-col md:justify-center p-4 lg:p-10 lg:pb-2 md:min-h-screen">
354+
const [email, setEmail] = useState("");
355+
const [isSubmitted, setIsSubmitted] = useState(false);
356+
const { toast } = useToast();
357+
358+
const handleEmailSubmit = (e: React.FormEvent) => {
359+
e.preventDefault();
360+
if (!email.trim()) return;
361+
362+
trackEvent("waitlist_joined", { email: email, feature: "Ona" });
363+
364+
setIsSubmitted(true);
365+
366+
toast(
324367
<div>
325-
<div className="justify-center md:justify-start mb-6 md:mb-8">
326-
<h2 className="text-2xl font-medium mb-2 dark:text-white inline-flex items-center gap-x-2">
327-
Gitpod Classic
368+
<div className="font-medium">You're on the waitlist</div>
369+
<div className="text-sm opacity-80">We'll reach out to you soon.</div>
370+
</div>,
371+
);
372+
};
373+
374+
return (
375+
<div className="w-full lg:max-w-lg 2xl:max-w-[600px] flex flex-col justify-center px-4 lg:px-4 md:min-h-screen my-auto">
376+
<div className="rounded-lg flex flex-col gap-6 text-white h-full py-4 lg:py-6 max-w-lg mx-auto w-full">
377+
<div className="relative bg-white/10 backdrop-blur-sm rounded-lg pt-4 px-4 -mt-2">
378+
<div className="flex justify-center pt-4 mb-4">
379+
<img src={onaWordmark} alt="ONA" className="w-36" draggable="false" />
380+
</div>
381+
<div className="relative overflow-hidden">
382+
<img
383+
src={onaApplication}
384+
alt="Ona application preview"
385+
className="w-full h-auto rounded-lg shadow-lg translate-y-8"
386+
draggable="false"
387+
/>
388+
</div>
389+
</div>
390+
391+
<div className="flex flex-col gap-4 flex-1">
392+
<h2 className="text-white text-xl font-bold leading-tight text-center max-w-sm mx-auto">
393+
Meet Ona - the privacy-first software engineering agent.
328394
</h2>
329-
<p className="text-pk-content-secondary mb-2">
330-
Automated, standardized development environments hosted by us in Gitpod’s infrastructure. Users
331-
who joined before October 1, 2024 on non-Enterprise plans are considered Gitpod Classic users.
332-
</p>
333395

334-
<p className="text-pk-content-secondary mb-2">
335-
Gitpod Classic is sunsetting fall 2025.{" "}
336-
<a className="gp-link font-bold" href="https://app.gitpod.io" target="_blank" rel="noreferrer">
337-
Try the new Gitpod
338-
</a>{" "}
339-
now (hosted compute coming soon).
340-
</p>
396+
<div className="space-y-3 mt-4">
397+
<p className="text-white/70 text-base">
398+
Delegate software tasks to Ona. It writes code, runs tests, and opens a pull request. Or
399+
jump in to inspect output or pair program in your IDE.
400+
</p>
401+
<p className="text-white/70 text-base mt-2">
402+
Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
403+
support for any LLM.
404+
</p>
405+
</div>
406+
407+
<div className="mt-4">
408+
{!isSubmitted ? (
409+
<form onSubmit={handleEmailSubmit} className="space-y-3">
410+
<div className="flex gap-2">
411+
<input
412+
type="email"
413+
value={email}
414+
onChange={(e) => setEmail(e.target.value)}
415+
placeholder="Enter your work email"
416+
className="flex-1 px-4 py-2.5 rounded-lg bg-white/10 backdrop-blur-sm border border-white/20 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/30 text-sm"
417+
required
418+
/>
419+
<button
420+
type="submit"
421+
className="bg-white text-gray-900 font-medium py-2.5 px-4 rounded-lg hover:bg-gray-100 transition-colors text-sm inline-flex items-center justify-center gap-2"
422+
>
423+
Request access
424+
<span className="font-bold"></span>
425+
</button>
426+
</div>
427+
<p className="text-xs text-white/70">
428+
By submitting this, you agree to our{" "}
429+
<a
430+
href="https://www.gitpod.io/privacy/"
431+
target="_blank"
432+
rel="noopener noreferrer"
433+
className="underline hover:text-white"
434+
>
435+
privacy policy
436+
</a>
437+
</p>
438+
</form>
439+
) : (
440+
<a
441+
href="https://www.gitpod.io/solutions/ai"
442+
target="_blank"
443+
rel="noopener noreferrer"
444+
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"
445+
>
446+
Learn more
447+
<span className="font-bold"></span>
448+
</a>
449+
)}
450+
</div>
341451
</div>
342-
<img src={GitpodClassicCard} alt="Gitpod Classic" className="w-full" />
343452
</div>
344453
</div>
345454
);

0 commit comments

Comments
 (0)