zeta: Update onboarding modal with subscription info (#30439)

This PR updates the edit prediction onboarding modal with steps about
subscribing to a plan.

When the user is not subscribed to a plan, we display a link to the
account page to sign up for one:

<img width="612" alt="Screenshot 2025-05-09 at 6 04 05 PM"
src="https://github.com/user-attachments/assets/0300194a-c419-43d9-8214-080674d31e12"
/>

If the user is already subscribed to a plan we indicate which plan they
are on and how many edit predictions they get with it:

<img width="616" alt="Screenshot 2025-05-09 at 6 03 16 PM"
src="https://github.com/user-attachments/assets/e2506096-e499-41f2-ba1f-fca768cb48b9"
/>

<img width="595" alt="Screenshot 2025-05-09 at 5 46 18 PM"
src="https://github.com/user-attachments/assets/de82f8c2-cad8-45fb-8988-26606a8dc3e1"
/>

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-05-09 18:46:10 -04:00 committed by GitHub
parent bff259731f
commit fbeee1f832
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 4 deletions

1
Cargo.lock generated
View file

@ -18952,6 +18952,7 @@ dependencies = [
"paths",
"postage",
"project",
"proto",
"regex",
"release_channel",
"reqwest_client",

View file

@ -237,11 +237,17 @@ impl Render for InlineCompletionButton {
let current_user_terms_accepted =
self.user_store.read(cx).current_user_has_accepted_terms();
let has_subscription = self.user_store.read(cx).current_plan().is_some()
&& self.user_store.read(cx).subscription_period().is_some();
if !current_user_terms_accepted.unwrap_or(false) {
if !has_subscription || !current_user_terms_accepted.unwrap_or(false) {
let signed_in = current_user_terms_accepted.is_some();
let tooltip_meta = if signed_in {
"Read Terms of Service"
if has_subscription {
"Read Terms of Service"
} else {
"Choose a Plan"
}
} else {
"Sign in to use"
};

View file

@ -39,6 +39,7 @@ migrator.workspace = true
paths.workspace = true
postage.workspace = true
project.workspace = true
proto.workspace = true
regex.workspace = true
release_channel.workspace = true
serde.workspace = true

View file

@ -2,7 +2,7 @@ use std::{sync::Arc, time::Duration};
use crate::{ZED_PREDICT_DATA_COLLECTION_CHOICE, onboarding_event};
use anyhow::Context as _;
use client::{Client, UserStore};
use client::{Client, UserStore, zed_urls};
use db::kvp::KEY_VALUE_STORE;
use fs::Fs;
use gpui::{
@ -246,6 +246,12 @@ impl Render for ZedPredictModal {
let window_height = window.viewport_size().height;
let max_height = window_height - px(200.);
let has_subscription_period = self.user_store.read(cx).subscription_period().is_some();
let plan = self.user_store.read(cx).current_plan().filter(|_| {
// Since the user might be on the legacy free plan we filter based on whether we have a subscription period.
has_subscription_period
});
let base = v_flex()
.id("edit-prediction-onboarding")
.key_context("ZedPredictModal")
@ -377,6 +383,45 @@ impl Render for ZedPredictModal {
};
base.child(Label::new(copy).color(Color::Muted))
.child(h_flex().map(|parent| {
if let Some(plan) = plan {
parent.child(
Checkbox::new("plan", ToggleState::Selected)
.fill()
.disabled(true)
.label(format!(
"You get {} edit predictions through your {}.",
if plan == proto::Plan::Free {
"2,000"
} else {
"unlimited"
},
match plan {
proto::Plan::Free => "Zed Free plan",
proto::Plan::ZedPro => "Zed Pro plan",
proto::Plan::ZedProTrial => "Zed Pro trial",
}
)),
)
} else {
parent
.child(
Checkbox::new("plan-required", ToggleState::Unselected)
.fill()
.disabled(true)
.label("To get started with edit prediction"),
)
.child(
Button::new("subscribe", "choose a plan")
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Indicator)
.icon_color(Color::Muted)
.on_click(|_event, _window, cx| {
cx.open_url(&zed_urls::account_url(cx));
}),
)
}
}))
.child(
h_flex()
.child(
@ -447,7 +492,7 @@ impl Render for ZedPredictModal {
.w_full()
.child(
Button::new("accept-tos", "Enable Edit Prediction")
.disabled(!self.terms_of_service)
.disabled(plan.is_none() || !self.terms_of_service)
.style(ButtonStyle::Tinted(TintColor::Accent))
.full_width()
.on_click(cx.listener(Self::accept_and_enable)),