Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 130 additions & 22 deletions components/dashboard/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import { cn } from "@podkit/lib/cn";
import { userClient } from "./service/public-api";
import { ProductLogo } from "./components/ProductLogo";
import { useIsDataOps } from "./data/featureflag-query";
import GitpodClassicCard from "./images/gitpod-classic-card.png";
import { LoadingState } from "@podkit/loading/LoadingState";
import { isGitpodIo } from "./utils";
import { trackEvent } from "./Analytics";
import { useToast } from "./components/toasts/Toasts";
import onaWordmark from "./images/ona-wordmark.svg";
import onaApplication from "./images/ona-application.webp";

export function markLoggedIn() {
document.cookie = GitpodCookie.generateCookie(window.location.hostname);
Expand Down Expand Up @@ -93,9 +96,15 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
return (
<div
id="login-container"
className={cn("z-50 flex flex-col-reverse lg:flex-row w-full min-h-screen", {
"bg-[#FDF1E7] dark:bg-[#23211e]": !enterprise,
})}
className={cn("z-50 flex flex-col-reverse lg:flex-row w-full min-h-screen")}
style={
!enterprise
? {
background:
"linear-gradient(390deg, #1F1329 0%, #333A75 20%, #556CA8 50%, #90A898 60%, #90A898 70%, #E2B15C 90%, #BEA462 100%)",
}
: undefined
}
>
{enterprise ? (
<EnterpriseLoginWrapper
Expand Down Expand Up @@ -147,7 +156,7 @@ const PAYGLoginWrapper: FC<LoginWrapperProps> = ({ providerFromContext, repoPath
<div
id="login-section"
// for some reason, min-h-dvh does not work, so we need tailwind's arbitrary values
className="w-full min-h-[100dvh] lg:w-2/3 flex flex-col justify-center items-center bg-[#FDF1E7] dark:bg-[#23211e] p-2"
className="w-full min-h-[100dvh] lg:w-2/3 flex flex-col justify-center items-center p-2"
>
<div
id="login-section-column"
Expand Down Expand Up @@ -212,6 +221,8 @@ const LoginContent = ({
const authProviders = useAuthProviderDescriptions();
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);

const enterprise = !!authProviders.data && authProviders.data.length === 0;

const updateUser = useCallback(async () => {
await getGitpodService().reconnect();
const { user } = await userClient.getAuthenticatedUser({});
Expand Down Expand Up @@ -314,32 +325,129 @@ const LoginContent = ({
<SSOLoginForm onSuccess={authorizeSuccessful} />
</div>
{errorMessage && <ErrorMessage imgSrc={exclamation} message={errorMessage} />}

{/* Gitpod Classic sunset notice - only show for non-enterprise */}
{!enterprise && (
<div className="mt-6 text-center text-sm bg-[#FDF1E7] dark:bg-[#23211e] p-2 rounded-lg">
<p className="text-pk-content-primary">
Gitpod classic is sunsetting fall 2025.{" "}
<a
href="https://app.gitpod.io"
target="_blank"
rel="noopener noreferrer"
className="gp-link hover:text-gray-600"
>
Try the new Gitpod
</a>{" "}
now (hosted compute coming soon)
</p>
</div>
)}
</div>
);
};

const RightProductDescriptionPanel = () => {
return (
<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">
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(
<div>
<div className="justify-center md:justify-start mb-6 md:mb-8">
<h2 className="text-2xl font-medium mb-2 dark:text-white inline-flex items-center gap-x-2">
Gitpod Classic
<div className="font-medium">You're on the waitlist</div>
<div className="text-sm opacity-80">We'll reach out to you soon.</div>
</div>,
);
};

const handleLearnMore = () => {
window.open("https://ona.com/", "_blank", "noopener,noreferrer");
};

return (
<div className="w-full lg:w-1/3 flex flex-col justify-center px-4 lg:px-4 md:min-h-screen">
<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">
<div className="relative bg-white/10 backdrop-blur-sm rounded-lg pt-4 px-4 -mt-2">
<div className="flex justify-center pt-4 mb-4">
<img src={onaWordmark} alt="ONA" className="w-36" draggable="false" />
</div>
<div className="relative overflow-hidden">
<img
src={onaApplication}
alt="Ona application preview"
className="w-full h-auto rounded-lg shadow-lg translate-y-8"
draggable="false"
/>
</div>
</div>

<div className="flex flex-col gap-4 flex-1">
<h2 className="text-white text-2xl font-bold leading-tight text-center max-w-sm mx-auto">
Meet Ona - the privacy-first software engineering agent.
</h2>
<p className="text-pk-content-secondary mb-2">
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.
</p>

<p className="text-pk-content-secondary mb-2">
Gitpod Classic is sunsetting fall 2025.{" "}
<a className="gp-link font-bold" href="https://app.gitpod.io" target="_blank" rel="noreferrer">
Try the new Gitpod
</a>{" "}
now (hosted compute coming soon).
</p>
<div className="space-y-3 mt-4">
<p className="text-white/70 text-xl">
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.
</p>
<p className="text-white/70 text-xl mt-2">
Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
support for any LLM.
</p>
</div>

<div className="mt-auto pt-4">
{!isSubmitted ? (
<form onSubmit={handleEmailSubmit} className="space-y-3">
<div className="flex gap-2">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your work email"
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"
required
/>
<button
type="submit"
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"
>
Request access
<span className="font-bold">→</span>
</button>
</div>
<p className="text-xs text-white/70">
By submitting this, you agree to our{" "}
<a
href="https://www.gitpod.io/privacy/"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-white"
>
privacy policy
</a>
</p>
</form>
) : (
<button
onClick={handleLearnMore}
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
<span className="font-bold">→</span>
</button>
)}
</div>
</div>
<img src={GitpodClassicCard} alt="Gitpod Classic" className="w-full" />
</div>
</div>
);
Expand Down
118 changes: 118 additions & 0 deletions components/dashboard/src/OnaRightPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* 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(
<div>
<div className="font-medium">You're on the waitlist</div>
<div className="text-sm opacity-80">We'll reach out to you soon.</div>
</div>,
);
};

return (
<div className="w-full lg:w-1/3 flex flex-col justify-center p-4 lg:p-6 md:min-h-screen">
<div
className="rounded-lg flex flex-col gap-6 text-white p-6 h-full max-w-md mx-auto w-full"
style={{
background:
"linear-gradient(340deg, #1F1329 0%, #333A75 20%, #556CA8 40%, #90A898 60%, #E2B15C 80%, #BEA462 100%)",
}}
>
<div className="flex justify-center pt-4">
<img src={onaWordmark} alt="ONA" className="w-32" draggable="false" />
</div>

<div className="relative bg-white/10 backdrop-blur-sm rounded-lg p-4 -mt-2">
<img
src={onaApplication}
alt="Ona application preview"
className="w-full h-auto rounded-lg shadow-lg"
draggable="false"
/>
</div>

<div className="flex flex-col gap-4 flex-1">
<h2 className="text-white text-xl font-bold leading-tight text-center">
Meet Ona - the privacy-first software engineering agent.
</h2>

<div className="space-y-3 text-sm text-white/90 leading-relaxed">
<p>
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.
</p>
<p>
Ona runs inside your infrastructure (VPC), with full audit trails, zero data exposure, and
support for any LLM.
</p>
</div>

<div className="mt-auto pt-4">
{!isSubmitted ? (
<form onSubmit={handleEmailSubmit} className="space-y-3">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your work email"
className="w-full 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"
required
/>
<button
type="submit"
className="w-full 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"
>
Request access
<span className="font-bold">→</span>
</button>
</form>
) : (
<button
onClick={() => window.open("https://ona.com/", "_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
<span className="font-bold">→</span>
</button>
)}
</div>
</div>
</div>
</div>
);
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading