1use std::sync::Arc;
2
3use client::{Client, zed_urls};
4use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
5use ui::{Divider, List, Vector, VectorName, prelude::*};
6
7use crate::{BulletItem, SignInStatus};
8
9#[derive(IntoElement, RegisterComponent)]
10pub struct AiUpsellCard {
11 pub sign_in_status: SignInStatus,
12 pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
13}
14
15impl AiUpsellCard {
16 pub fn new(client: Arc<Client>) -> Self {
17 let status = *client.status().borrow();
18
19 Self {
20 sign_in_status: status.into(),
21 sign_in: Arc::new(move |_window, cx| {
22 cx.spawn({
23 let client = client.clone();
24 async move |cx| {
25 client.authenticate_and_connect(true, cx).await;
26 }
27 })
28 .detach();
29 }),
30 }
31 }
32}
33
34impl RenderOnce for AiUpsellCard {
35 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
36 let pro_section = v_flex()
37 .w_full()
38 .gap_1()
39 .child(
40 h_flex()
41 .gap_2()
42 .child(
43 Label::new("Pro")
44 .size(LabelSize::Small)
45 .color(Color::Accent)
46 .buffer_font(cx),
47 )
48 .child(Divider::horizontal()),
49 )
50 .child(
51 List::new()
52 .child(BulletItem::new("500 prompts with Claude models"))
53 .child(BulletItem::new(
54 "Unlimited edit predictions with Zeta, our open-source model",
55 )),
56 );
57
58 let free_section = v_flex()
59 .w_full()
60 .gap_1()
61 .child(
62 h_flex()
63 .gap_2()
64 .child(
65 Label::new("Free")
66 .size(LabelSize::Small)
67 .color(Color::Muted)
68 .buffer_font(cx),
69 )
70 .child(Divider::horizontal()),
71 )
72 .child(
73 List::new()
74 .child(BulletItem::new("50 prompts with the Claude models"))
75 .child(BulletItem::new("2,000 accepted edit predictions")),
76 );
77
78 let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
79 Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
80 .color(Color::Custom(cx.theme().colors().border.opacity(0.05))),
81 );
82
83 let gradient_bg = div()
84 .absolute()
85 .inset_0()
86 .size_full()
87 .bg(gpui::linear_gradient(
88 180.,
89 gpui::linear_color_stop(
90 cx.theme().colors().elevated_surface_background.opacity(0.8),
91 0.,
92 ),
93 gpui::linear_color_stop(
94 cx.theme().colors().elevated_surface_background.opacity(0.),
95 0.8,
96 ),
97 ));
98
99 const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
100
101 let footer_buttons = match self.sign_in_status {
102 SignInStatus::SignedIn => v_flex()
103 .items_center()
104 .gap_1()
105 .child(
106 Button::new("sign_in", "Start 14-day Free Pro Trial")
107 .full_width()
108 .style(ButtonStyle::Tinted(ui::TintColor::Accent))
109 .on_click(move |_, _window, cx| {
110 telemetry::event!("Start Trial Clicked", state = "post-sign-in");
111 cx.open_url(&zed_urls::start_trial_url(cx))
112 }),
113 )
114 .child(
115 Label::new("No credit card required")
116 .size(LabelSize::Small)
117 .color(Color::Muted),
118 )
119 .into_any_element(),
120 _ => Button::new("sign_in", "Sign In")
121 .full_width()
122 .style(ButtonStyle::Tinted(ui::TintColor::Accent))
123 .on_click({
124 let callback = self.sign_in.clone();
125 move |_, window, cx| {
126 telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
127 callback(window, cx)
128 }
129 })
130 .into_any_element(),
131 };
132
133 v_flex()
134 .relative()
135 .p_6()
136 .pt_4()
137 .border_1()
138 .border_color(cx.theme().colors().border)
139 .rounded_lg()
140 .overflow_hidden()
141 .child(grid_bg)
142 .child(gradient_bg)
143 .child(Headline::new("Try Zed AI"))
144 .child(Label::new(DESCRIPTION).color(Color::Muted).mb_2())
145 .child(
146 h_flex()
147 .mt_1p5()
148 .mb_2p5()
149 .items_start()
150 .gap_12()
151 .child(free_section)
152 .child(pro_section),
153 )
154 .child(footer_buttons)
155 }
156}
157
158impl Component for AiUpsellCard {
159 fn scope() -> ComponentScope {
160 ComponentScope::Agent
161 }
162
163 fn name() -> &'static str {
164 "AI Upsell Card"
165 }
166
167 fn sort_name() -> &'static str {
168 "AI Upsell Card"
169 }
170
171 fn description() -> Option<&'static str> {
172 Some("A card presenting the Zed AI product during user's first-open onboarding flow.")
173 }
174
175 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
176 Some(
177 v_flex()
178 .p_4()
179 .gap_4()
180 .children(vec![example_group(vec![
181 single_example(
182 "Signed Out State",
183 AiUpsellCard {
184 sign_in_status: SignInStatus::SignedOut,
185 sign_in: Arc::new(|_, _| {}),
186 }
187 .into_any_element(),
188 ),
189 single_example(
190 "Signed In State",
191 AiUpsellCard {
192 sign_in_status: SignInStatus::SignedIn,
193 sign_in: Arc::new(|_, _| {}),
194 }
195 .into_any_element(),
196 ),
197 ])])
198 .into_any_element(),
199 )
200 }
201}