diff --git a/assets/images/certified_user_stamp.svg b/assets/images/certified_user_stamp.svg
new file mode 100644
index 0000000000..7e65c4fc9d
--- /dev/null
+++ b/assets/images/certified_user_stamp.svg
@@ -0,0 +1 @@
+
diff --git a/assets/images/pro_trial_stamp.svg b/assets/images/pro_trial_stamp.svg
new file mode 100644
index 0000000000..501de88a48
--- /dev/null
+++ b/assets/images/pro_trial_stamp.svg
@@ -0,0 +1 @@
+
diff --git a/crates/agent_ui/src/ui/end_trial_upsell.rs b/crates/agent_ui/src/ui/end_trial_upsell.rs
index 36770c2197..0d9751afec 100644
--- a/crates/agent_ui/src/ui/end_trial_upsell.rs
+++ b/crates/agent_ui/src/ui/end_trial_upsell.rs
@@ -1,9 +1,9 @@
use std::sync::Arc;
-use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
+use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
use client::zed_urls;
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
-use ui::{Divider, List, Tooltip, prelude::*};
+use ui::{Divider, Tooltip, prelude::*};
#[derive(IntoElement, RegisterComponent)]
pub struct EndTrialUpsell {
@@ -18,6 +18,8 @@ impl EndTrialUpsell {
impl RenderOnce for EndTrialUpsell {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let plan_definitions = PlanDefinitions;
+
let pro_section = v_flex()
.gap_1()
.child(
@@ -31,13 +33,7 @@ impl RenderOnce for EndTrialUpsell {
)
.child(Divider::horizontal()),
)
- .child(
- List::new()
- .child(BulletItem::new("500 prompts with Claude models"))
- .child(BulletItem::new(
- "Unlimited edit predictions with Zeta, our open-source model",
- )),
- )
+ .child(plan_definitions.pro_plan(false))
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.full_width()
@@ -68,11 +64,7 @@ impl RenderOnce for EndTrialUpsell {
)
.child(Divider::horizontal()),
)
- .child(
- List::new()
- .child(BulletItem::new("50 prompts with the Claude models"))
- .child(BulletItem::new("2,000 accepted edit predictions")),
- );
+ .child(plan_definitions.free_plan());
AgentPanelOnboardingCard::new()
.child(Headline::new("Your Zed Pro Trial has expired"))
diff --git a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
index e86568fe7a..b55ad4c895 100644
--- a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
+++ b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs
@@ -1,8 +1,6 @@
use gpui::{Action, IntoElement, ParentElement, RenderOnce, point};
use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
-use ui::{Divider, List, prelude::*};
-
-use crate::BulletItem;
+use ui::{Divider, List, ListBulletItem, prelude::*};
pub struct ApiKeysWithProviders {
configured_providers: Vec<(IconName, SharedString)>,
@@ -128,7 +126,7 @@ impl RenderOnce for ApiKeysWithoutProviders {
)
.child(Divider::horizontal()),
)
- .child(List::new().child(BulletItem::new(
+ .child(List::new().child(ListBulletItem::new(
"Add your own keys to use AI without signing in.",
)))
.child(
diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs
index c252b65f20..9372a33fed 100644
--- a/crates/ai_onboarding/src/ai_onboarding.rs
+++ b/crates/ai_onboarding/src/ai_onboarding.rs
@@ -3,6 +3,7 @@ mod agent_panel_onboarding_card;
mod agent_panel_onboarding_content;
mod ai_upsell_card;
mod edit_prediction_onboarding_content;
+mod plan_definitions;
mod young_account_banner;
pub use agent_api_keys_onboarding::{ApiKeysWithProviders, ApiKeysWithoutProviders};
@@ -11,51 +12,14 @@ pub use agent_panel_onboarding_content::AgentPanelOnboarding;
pub use ai_upsell_card::AiUpsellCard;
use cloud_llm_client::Plan;
pub use edit_prediction_onboarding_content::EditPredictionOnboarding;
+pub use plan_definitions::PlanDefinitions;
pub use young_account_banner::YoungAccountBanner;
use std::sync::Arc;
use client::{Client, UserStore, zed_urls};
-use gpui::{AnyElement, Entity, IntoElement, ParentElement, SharedString};
-use ui::{Divider, List, ListItem, RegisterComponent, TintColor, Tooltip, prelude::*};
-
-#[derive(IntoElement)]
-pub struct BulletItem {
- label: SharedString,
-}
-
-impl BulletItem {
- pub fn new(label: impl Into) -> Self {
- Self {
- label: label.into(),
- }
- }
-}
-
-impl RenderOnce for BulletItem {
- fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
- let line_height = 0.85 * window.line_height();
-
- ListItem::new("list-item")
- .selectable(false)
- .child(
- h_flex()
- .w_full()
- .min_w_0()
- .gap_1()
- .items_start()
- .child(
- h_flex().h(line_height).justify_center().child(
- Icon::new(IconName::Dash)
- .size(IconSize::XSmall)
- .color(Color::Hidden),
- ),
- )
- .child(div().w_full().min_w_0().child(Label::new(self.label))),
- )
- .into_any_element()
- }
-}
+use gpui::{AnyElement, Entity, IntoElement, ParentElement};
+use ui::{Divider, RegisterComponent, TintColor, Tooltip, prelude::*};
#[derive(PartialEq)]
pub enum SignInStatus {
@@ -130,107 +94,6 @@ impl ZedAiOnboarding {
self
}
- fn free_plan_definition(&self, cx: &mut App) -> impl IntoElement {
- v_flex()
- .mt_2()
- .gap_1()
- .child(
- h_flex()
- .gap_2()
- .child(
- Label::new("Free")
- .size(LabelSize::Small)
- .color(Color::Muted)
- .buffer_font(cx),
- )
- .child(
- Label::new("(Current Plan)")
- .size(LabelSize::Small)
- .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.6)))
- .buffer_font(cx),
- )
- .child(Divider::horizontal()),
- )
- .child(
- List::new()
- .child(BulletItem::new("50 prompts per month with Claude models"))
- .child(BulletItem::new(
- "2,000 accepted edit predictions with Zeta, our open-source model",
- )),
- )
- }
-
- fn pro_trial_definition(&self) -> impl IntoElement {
- List::new()
- .child(BulletItem::new("150 prompts with Claude models"))
- .child(BulletItem::new(
- "Unlimited accepted edit predictions with Zeta, our open-source model",
- ))
- }
-
- fn pro_plan_definition(&self, cx: &mut App) -> impl IntoElement {
- v_flex().mt_2().gap_1().map(|this| {
- if self.account_too_young {
- this.child(
- h_flex()
- .gap_2()
- .child(
- Label::new("Pro")
- .size(LabelSize::Small)
- .color(Color::Accent)
- .buffer_font(cx),
- )
- .child(Divider::horizontal()),
- )
- .child(
- List::new()
- .child(BulletItem::new("500 prompts per month with Claude models"))
- .child(BulletItem::new(
- "Unlimited accepted edit predictions with Zeta, our open-source model",
- ))
- .child(BulletItem::new("$20 USD per month")),
- )
- .child(
- Button::new("pro", "Get Started")
- .full_width()
- .style(ButtonStyle::Tinted(ui::TintColor::Accent))
- .on_click(move |_, _window, cx| {
- telemetry::event!("Upgrade To Pro Clicked", state = "young-account");
- cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
- }),
- )
- } else {
- this.child(
- h_flex()
- .gap_2()
- .child(
- Label::new("Pro Trial")
- .size(LabelSize::Small)
- .color(Color::Accent)
- .buffer_font(cx),
- )
- .child(Divider::horizontal()),
- )
- .child(
- List::new()
- .child(self.pro_trial_definition())
- .child(BulletItem::new(
- "Try it out for 14 days for free, no credit card required",
- )),
- )
- .child(
- Button::new("pro", "Start Free Trial")
- .full_width()
- .style(ButtonStyle::Tinted(ui::TintColor::Accent))
- .on_click(move |_, _window, cx| {
- telemetry::event!("Start Trial Clicked", state = "post-sign-in");
- cx.open_url(&zed_urls::start_trial_url(cx))
- }),
- )
- }
- })
- }
-
fn render_accept_terms_of_service(&self) -> AnyElement {
v_flex()
.gap_1()
@@ -269,6 +132,7 @@ impl ZedAiOnboarding {
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
+ let plan_definitions = PlanDefinitions;
v_flex()
.gap_1()
@@ -278,7 +142,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(self.pro_trial_definition())
+ .child(plan_definitions.pro_plan(false))
.child(
Button::new("sign_in", "Try Zed Pro for Free")
.disabled(signing_in)
@@ -297,43 +161,132 @@ impl ZedAiOnboarding {
fn render_free_plan_state(&self, cx: &mut App) -> AnyElement {
let young_account_banner = YoungAccountBanner;
+ let plan_definitions = PlanDefinitions;
- v_flex()
- .relative()
- .gap_1()
- .child(Headline::new("Welcome to Zed AI"))
- .map(|this| {
- if self.account_too_young {
- this.child(young_account_banner)
- } else {
- this.child(self.free_plan_definition(cx)).when_some(
- self.dismiss_onboarding.as_ref(),
- |this, dismiss_callback| {
- let callback = dismiss_callback.clone();
+ if self.account_too_young {
+ v_flex()
+ .relative()
+ .max_w_full()
+ .gap_1()
+ .child(Headline::new("Welcome to Zed AI"))
+ .child(young_account_banner)
+ .child(
+ v_flex()
+ .mt_2()
+ .gap_1()
+ .child(
+ h_flex()
+ .gap_2()
+ .child(
+ Label::new("Pro")
+ .size(LabelSize::Small)
+ .color(Color::Accent)
+ .buffer_font(cx),
+ )
+ .child(Divider::horizontal()),
+ )
+ .child(plan_definitions.pro_plan(true))
+ .child(
+ Button::new("pro", "Get Started")
+ .full_width()
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .on_click(move |_, _window, cx| {
+ telemetry::event!(
+ "Upgrade To Pro Clicked",
+ state = "young-account"
+ );
+ cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
+ }),
+ ),
+ )
+ .into_any_element()
+ } else {
+ v_flex()
+ .relative()
+ .gap_1()
+ .child(Headline::new("Welcome to Zed AI"))
+ .child(
+ v_flex()
+ .mt_2()
+ .gap_1()
+ .child(
+ h_flex()
+ .gap_2()
+ .child(
+ Label::new("Free")
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .buffer_font(cx),
+ )
+ .child(
+ Label::new("(Current Plan)")
+ .size(LabelSize::Small)
+ .color(Color::Custom(
+ cx.theme().colors().text_muted.opacity(0.6),
+ ))
+ .buffer_font(cx),
+ )
+ .child(Divider::horizontal()),
+ )
+ .child(plan_definitions.free_plan()),
+ )
+ .when_some(
+ self.dismiss_onboarding.as_ref(),
+ |this, dismiss_callback| {
+ let callback = dismiss_callback.clone();
- this.child(
- h_flex().absolute().top_0().right_0().child(
- IconButton::new("dismiss_onboarding", IconName::Close)
- .icon_size(IconSize::Small)
- .tooltip(Tooltip::text("Dismiss"))
- .on_click(move |_, window, cx| {
- telemetry::event!(
- "Banner Dismissed",
- source = "AI Onboarding",
- );
- callback(window, cx)
- }),
- ),
- )
- },
- )
- }
- })
- .child(self.pro_plan_definition(cx))
- .into_any_element()
+ this.child(
+ h_flex().absolute().top_0().right_0().child(
+ IconButton::new("dismiss_onboarding", IconName::Close)
+ .icon_size(IconSize::Small)
+ .tooltip(Tooltip::text("Dismiss"))
+ .on_click(move |_, window, cx| {
+ telemetry::event!(
+ "Banner Dismissed",
+ source = "AI Onboarding",
+ );
+ callback(window, cx)
+ }),
+ ),
+ )
+ },
+ )
+ .child(
+ v_flex()
+ .mt_2()
+ .gap_1()
+ .child(
+ h_flex()
+ .gap_2()
+ .child(
+ Label::new("Pro Trial")
+ .size(LabelSize::Small)
+ .color(Color::Accent)
+ .buffer_font(cx),
+ )
+ .child(Divider::horizontal()),
+ )
+ .child(plan_definitions.pro_trial(true))
+ .child(
+ Button::new("pro", "Start Free Trial")
+ .full_width()
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .on_click(move |_, _window, cx| {
+ telemetry::event!(
+ "Start Trial Clicked",
+ state = "post-sign-in"
+ );
+ cx.open_url(&zed_urls::start_trial_url(cx))
+ }),
+ ),
+ )
+ .into_any_element()
+ }
}
fn render_trial_state(&self, _cx: &mut App) -> AnyElement {
+ let plan_definitions = PlanDefinitions;
+
v_flex()
.relative()
.gap_1()
@@ -343,13 +296,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(
- List::new()
- .child(BulletItem::new("150 prompts with Claude models"))
- .child(BulletItem::new(
- "Unlimited edit predictions with Zeta, our open-source model",
- )),
- )
+ .child(plan_definitions.pro_trial(false))
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
@@ -374,6 +321,8 @@ impl ZedAiOnboarding {
}
fn render_pro_plan_state(&self, _cx: &mut App) -> AnyElement {
+ let plan_definitions = PlanDefinitions;
+
v_flex()
.gap_1()
.child(Headline::new("Welcome to Zed Pro"))
@@ -382,13 +331,7 @@ impl ZedAiOnboarding {
.color(Color::Muted)
.mb_2(),
)
- .child(
- List::new()
- .child(BulletItem::new("500 prompts with Claude models"))
- .child(BulletItem::new(
- "Unlimited edit predictions with Zeta, our open-source model",
- )),
- )
+ .child(plan_definitions.pro_plan(false))
.child(
Button::new("pro", "Continue with Zed Pro")
.full_width()
@@ -450,8 +393,9 @@ impl Component for ZedAiOnboarding {
Some(
v_flex()
- .p_4()
.gap_4()
+ .items_center()
+ .max_w_4_5()
.children(vec![
single_example(
"Not Signed-in",
@@ -462,8 +406,8 @@ impl Component for ZedAiOnboarding {
onboarding(SignInStatus::SignedIn, false, None, false),
),
single_example(
- "Account too young",
- onboarding(SignInStatus::SignedIn, false, None, true),
+ "Young Account",
+ onboarding(SignInStatus::SignedIn, true, None, true),
),
single_example(
"Free Plan",
diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs
index 89a782a7c2..a3fea5dce3 100644
--- a/crates/ai_onboarding/src/ai_upsell_card.rs
+++ b/crates/ai_onboarding/src/ai_upsell_card.rs
@@ -1,11 +1,14 @@
-use std::sync::Arc;
+use std::{sync::Arc, time::Duration};
use client::{Client, zed_urls};
use cloud_llm_client::Plan;
-use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
-use ui::{Divider, List, Vector, VectorName, prelude::*};
+use gpui::{
+ Animation, AnimationExt, AnyElement, App, IntoElement, RenderOnce, Transformation, Window,
+ percentage,
+};
+use ui::{Divider, Vector, VectorName, prelude::*};
-use crate::{BulletItem, SignInStatus};
+use crate::{SignInStatus, plan_definitions::PlanDefinitions};
#[derive(IntoElement, RegisterComponent)]
pub struct AiUpsellCard {
@@ -36,6 +39,8 @@ impl AiUpsellCard {
impl RenderOnce for AiUpsellCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let plan_definitions = PlanDefinitions;
+
let pro_section = v_flex()
.flex_grow()
.w_full()
@@ -51,13 +56,7 @@ impl RenderOnce for AiUpsellCard {
)
.child(Divider::horizontal()),
)
- .child(
- List::new()
- .child(BulletItem::new("500 prompts with Claude models"))
- .child(BulletItem::new(
- "Unlimited edit predictions with Zeta, our open-source model",
- )),
- );
+ .child(plan_definitions.pro_plan(false));
let free_section = v_flex()
.flex_grow()
@@ -74,11 +73,7 @@ impl RenderOnce for AiUpsellCard {
)
.child(Divider::horizontal()),
)
- .child(
- List::new()
- .child(BulletItem::new("50 prompts with Claude models"))
- .child(BulletItem::new("2,000 accepted edit predictions")),
- );
+ .child(plan_definitions.free_plan());
let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
@@ -101,44 +96,11 @@ impl RenderOnce for AiUpsellCard {
),
));
- const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
+ let description = PlanDefinitions::AI_DESCRIPTION;
- let footer_buttons = match self.sign_in_status {
- SignInStatus::SignedIn => v_flex()
- .items_center()
- .gap_1()
- .child(
- Button::new("sign_in", "Start 14-day Free Pro Trial")
- .full_width()
- .style(ButtonStyle::Tinted(ui::TintColor::Accent))
- .on_click(move |_, _window, cx| {
- telemetry::event!("Start Trial Clicked", state = "post-sign-in");
- cx.open_url(&zed_urls::start_trial_url(cx))
- })
- .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)),
- )
- .child(
- Label::new("No credit card required")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .into_any_element(),
- _ => Button::new("sign_in", "Sign In")
- .full_width()
- .style(ButtonStyle::Tinted(ui::TintColor::Accent))
- .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
- .on_click({
- let callback = self.sign_in.clone();
- move |_, window, cx| {
- telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
- callback(window, cx)
- }
- })
- .into_any_element(),
- };
-
- v_flex()
+ let card = v_flex()
.relative()
+ .flex_grow()
.p_4()
.pt_3()
.border_1()
@@ -146,25 +108,129 @@ impl RenderOnce for AiUpsellCard {
.rounded_lg()
.overflow_hidden()
.child(grid_bg)
- .child(gradient_bg)
- .child(Label::new("Try Zed AI").size(LabelSize::Large))
+ .child(gradient_bg);
+
+ let plans_section = h_flex()
+ .w_full()
+ .mt_1p5()
+ .mb_2p5()
+ .items_start()
+ .gap_6()
+ .child(free_section)
+ .child(pro_section);
+
+ let footer_container = v_flex().items_center().gap_1();
+
+ let certified_user_stamp = div()
+ .absolute()
+ .top_2()
+ .right_2()
+ .size(rems_from_px(72.))
.child(
- div()
- .max_w_3_4()
- .mb_2()
- .child(Label::new(DESCRIPTION).color(Color::Muted)),
- )
+ Vector::new(
+ VectorName::CertifiedUserStamp,
+ rems_from_px(72.),
+ rems_from_px(72.),
+ )
+ .color(Color::Custom(cx.theme().colors().text_accent.alpha(0.3)))
+ .with_animation(
+ "loading_stamp",
+ Animation::new(Duration::from_secs(10)).repeat(),
+ |this, delta| this.transform(Transformation::rotate(percentage(delta))),
+ ),
+ );
+
+ let pro_trial_stamp = div()
+ .absolute()
+ .top_2()
+ .right_2()
+ .size(rems_from_px(72.))
.child(
- h_flex()
- .w_full()
- .mt_1p5()
- .mb_2p5()
- .items_start()
- .gap_6()
- .child(free_section)
- .child(pro_section),
- )
- .child(footer_buttons)
+ Vector::new(
+ VectorName::ProTrialStamp,
+ rems_from_px(72.),
+ rems_from_px(72.),
+ )
+ .color(Color::Custom(cx.theme().colors().text.alpha(0.2))),
+ );
+
+ match self.sign_in_status {
+ SignInStatus::SignedIn => match self.user_plan {
+ None | Some(Plan::ZedFree) => card
+ .child(Label::new("Try Zed AI").size(LabelSize::Large))
+ .child(
+ div()
+ .max_w_3_4()
+ .mb_2()
+ .child(Label::new(description).color(Color::Muted)),
+ )
+ .child(plans_section)
+ .child(
+ footer_container
+ .child(
+ Button::new("start_trial", "Start 14-day Free Pro Trial")
+ .full_width()
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .when_some(self.tab_index, |this, tab_index| {
+ this.tab_index(tab_index)
+ })
+ .on_click(move |_, _window, cx| {
+ telemetry::event!(
+ "Start Trial Clicked",
+ state = "post-sign-in"
+ );
+ cx.open_url(&zed_urls::start_trial_url(cx))
+ }),
+ )
+ .child(
+ Label::new("No credit card required")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ ),
+ ),
+ Some(Plan::ZedProTrial) => card
+ .child(pro_trial_stamp)
+ .child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large))
+ .child(
+ Label::new("Here's what you get for the next 14 days:")
+ .color(Color::Muted)
+ .mb_2(),
+ )
+ .child(plan_definitions.pro_trial(false)),
+ Some(Plan::ZedPro) => card
+ .child(certified_user_stamp)
+ .child(Label::new("You're in the Zed Pro plan").size(LabelSize::Large))
+ .child(
+ Label::new("Here's what you get:")
+ .color(Color::Muted)
+ .mb_2(),
+ )
+ .child(plan_definitions.pro_plan(false)),
+ },
+ // Signed Out State
+ _ => card
+ .child(Label::new("Try Zed AI").size(LabelSize::Large))
+ .child(
+ div()
+ .max_w_3_4()
+ .mb_2()
+ .child(Label::new(description).color(Color::Muted)),
+ )
+ .child(plans_section)
+ .child(
+ Button::new("sign_in", "Sign In")
+ .full_width()
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
+ .on_click({
+ let callback = self.sign_in.clone();
+ move |_, window, cx| {
+ telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
+ callback(window, cx)
+ }
+ }),
+ ),
+ }
}
}
@@ -188,7 +254,6 @@ impl Component for AiUpsellCard {
fn preview(_window: &mut Window, _cx: &mut App) -> Option {
Some(
v_flex()
- .p_4()
.gap_4()
.children(vec![example_group(vec![
single_example(
@@ -202,11 +267,31 @@ impl Component for AiUpsellCard {
.into_any_element(),
),
single_example(
- "Signed In State",
+ "Free Plan",
AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
- user_plan: None,
+ user_plan: Some(Plan::ZedFree),
+ tab_index: Some(1),
+ }
+ .into_any_element(),
+ ),
+ single_example(
+ "Pro Trial",
+ AiUpsellCard {
+ sign_in_status: SignInStatus::SignedIn,
+ sign_in: Arc::new(|_, _| {}),
+ user_plan: Some(Plan::ZedProTrial),
+ tab_index: Some(1),
+ }
+ .into_any_element(),
+ ),
+ single_example(
+ "Pro Plan",
+ AiUpsellCard {
+ sign_in_status: SignInStatus::SignedIn,
+ sign_in: Arc::new(|_, _| {}),
+ user_plan: Some(Plan::ZedPro),
tab_index: Some(1),
}
.into_any_element(),
diff --git a/crates/ai_onboarding/src/plan_definitions.rs b/crates/ai_onboarding/src/plan_definitions.rs
new file mode 100644
index 0000000000..8d66f6c356
--- /dev/null
+++ b/crates/ai_onboarding/src/plan_definitions.rs
@@ -0,0 +1,39 @@
+use gpui::{IntoElement, ParentElement};
+use ui::{List, ListBulletItem, prelude::*};
+
+/// Centralized definitions for Zed AI plans
+pub struct PlanDefinitions;
+
+impl PlanDefinitions {
+ pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
+
+ pub fn free_plan(&self) -> impl IntoElement {
+ List::new()
+ .child(ListBulletItem::new("50 prompts with Claude models"))
+ .child(ListBulletItem::new("2,000 accepted edit predictions"))
+ }
+
+ pub fn pro_trial(&self, period: bool) -> impl IntoElement {
+ List::new()
+ .child(ListBulletItem::new("150 prompts with Claude models"))
+ .child(ListBulletItem::new(
+ "Unlimited edit predictions with Zeta, our open-source model",
+ ))
+ .when(period, |this| {
+ this.child(ListBulletItem::new(
+ "Try it out for 14 days for free, no credit card required",
+ ))
+ })
+ }
+
+ pub fn pro_plan(&self, price: bool) -> impl IntoElement {
+ List::new()
+ .child(ListBulletItem::new("500 prompts with Claude models"))
+ .child(ListBulletItem::new(
+ "Unlimited edit predictions with Zeta, our open-source model",
+ ))
+ .when(price, |this| {
+ this.child(ListBulletItem::new("$20 USD per month"))
+ })
+ }
+}
diff --git a/crates/ai_onboarding/src/young_account_banner.rs b/crates/ai_onboarding/src/young_account_banner.rs
index a43625a60e..54f563e4aa 100644
--- a/crates/ai_onboarding/src/young_account_banner.rs
+++ b/crates/ai_onboarding/src/young_account_banner.rs
@@ -15,6 +15,7 @@ impl RenderOnce for YoungAccountBanner {
.child(YOUNG_ACCOUNT_DISCLAIMER);
div()
+ .max_w_full()
.my_1()
.child(Banner::new().severity(ui::Severity::Warning).child(label))
}
diff --git a/crates/ui/src/components/image.rs b/crates/ui/src/components/image.rs
index 2deba68d88..18f804abe9 100644
--- a/crates/ui/src/components/image.rs
+++ b/crates/ui/src/components/image.rs
@@ -1,5 +1,6 @@
use std::sync::Arc;
+use gpui::Transformation;
use gpui::{App, IntoElement, Rems, RenderOnce, Size, Styled, Window, svg};
use serde::{Deserialize, Serialize};
use strum::{EnumIter, EnumString, IntoStaticStr};
@@ -12,11 +13,13 @@ use crate::prelude::*;
)]
#[strum(serialize_all = "snake_case")]
pub enum VectorName {
+ AiGrid,
+ CertifiedUserStamp,
+ DebuggerGrid,
+ Grid,
+ ProTrialStamp,
ZedLogo,
ZedXCopilot,
- Grid,
- AiGrid,
- DebuggerGrid,
}
impl VectorName {
@@ -37,6 +40,7 @@ pub struct Vector {
path: Arc,
color: Color,
size: Size,
+ transformation: Transformation,
}
impl Vector {
@@ -46,6 +50,7 @@ impl Vector {
path: vector.path(),
color: Color::default(),
size: Size { width, height },
+ transformation: Transformation::default(),
}
}
@@ -66,6 +71,11 @@ impl Vector {
self.size = size;
self
}
+
+ pub fn transform(mut self, transformation: Transformation) -> Self {
+ self.transformation = transformation;
+ self
+ }
}
impl RenderOnce for Vector {
@@ -81,6 +91,7 @@ impl RenderOnce for Vector {
.h(height)
.path(self.path)
.text_color(self.color.color(cx))
+ .with_transformation(self.transformation)
}
}
diff --git a/crates/ui/src/components/list.rs b/crates/ui/src/components/list.rs
index 88650b6ae8..6876f290ce 100644
--- a/crates/ui/src/components/list.rs
+++ b/crates/ui/src/components/list.rs
@@ -1,10 +1,12 @@
mod list;
+mod list_bullet_item;
mod list_header;
mod list_item;
mod list_separator;
mod list_sub_header;
pub use list::*;
+pub use list_bullet_item::*;
pub use list_header::*;
pub use list_item::*;
pub use list_separator::*;
diff --git a/crates/ui/src/components/list/list_bullet_item.rs b/crates/ui/src/components/list/list_bullet_item.rs
new file mode 100644
index 0000000000..6e079d9f11
--- /dev/null
+++ b/crates/ui/src/components/list/list_bullet_item.rs
@@ -0,0 +1,40 @@
+use crate::{ListItem, prelude::*};
+use gpui::{IntoElement, ParentElement, SharedString};
+
+#[derive(IntoElement)]
+pub struct ListBulletItem {
+ label: SharedString,
+}
+
+impl ListBulletItem {
+ pub fn new(label: impl Into) -> Self {
+ Self {
+ label: label.into(),
+ }
+ }
+}
+
+impl RenderOnce for ListBulletItem {
+ fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
+ let line_height = 0.85 * window.line_height();
+
+ ListItem::new("list-item")
+ .selectable(false)
+ .child(
+ h_flex()
+ .w_full()
+ .min_w_0()
+ .gap_1()
+ .items_start()
+ .child(
+ h_flex().h(line_height).justify_center().child(
+ Icon::new(IconName::Dash)
+ .size(IconSize::XSmall)
+ .color(Color::Hidden),
+ ),
+ )
+ .child(div().w_full().min_w_0().child(Label::new(self.label))),
+ )
+ .into_any_element()
+ }
+}