upsell.rs

  1use component::{Component, ComponentScope, single_example};
  2use gpui::{
  3    AnyElement, App, ClickEvent, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
  4    Window,
  5};
  6use theme::ActiveTheme;
  7use ui::{
  8    Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Color, Label, LabelCommon,
  9    RegisterComponent, ToggleState, h_flex, v_flex,
 10};
 11
 12/// A component that displays an upsell message with a call-to-action button
 13///
 14/// # Example
 15/// ```
 16/// let upsell = Upsell::new(
 17///     "Upgrade to Zed Pro",
 18///     "Get access to advanced AI features and more",
 19///     "Upgrade Now",
 20///     Box::new(|_, _window, cx| {
 21///         cx.open_url("https://zed.dev/pricing");
 22///     }),
 23///     Box::new(|_, _window, cx| {
 24///         // Handle dismiss
 25///     }),
 26///     Box::new(|checked, window, cx| {
 27///         // Handle don't show again
 28///     }),
 29/// );
 30/// ```
 31#[derive(IntoElement, RegisterComponent)]
 32pub struct Upsell {
 33    title: SharedString,
 34    message: SharedString,
 35    cta_text: SharedString,
 36    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 37    on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 38    on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
 39}
 40
 41impl Upsell {
 42    /// Create a new upsell component
 43    pub fn new(
 44        title: impl Into<SharedString>,
 45        message: impl Into<SharedString>,
 46        cta_text: impl Into<SharedString>,
 47        on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 48        on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 49        on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
 50    ) -> Self {
 51        Self {
 52            title: title.into(),
 53            message: message.into(),
 54            cta_text: cta_text.into(),
 55            on_click,
 56            on_dismiss,
 57            on_dont_show_again,
 58        }
 59    }
 60}
 61
 62impl RenderOnce for Upsell {
 63    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 64        v_flex()
 65            .w_full()
 66            .p_4()
 67            .gap_3()
 68            .bg(cx.theme().colors().surface_background)
 69            .rounded_md()
 70            .border_1()
 71            .border_color(cx.theme().colors().border)
 72            .child(
 73                v_flex()
 74                    .gap_1()
 75                    .child(
 76                        Label::new(self.title)
 77                            .size(ui::LabelSize::Large)
 78                            .weight(gpui::FontWeight::BOLD),
 79                    )
 80                    .child(Label::new(self.message).color(Color::Muted)),
 81            )
 82            .child(
 83                h_flex()
 84                    .w_full()
 85                    .justify_between()
 86                    .items_center()
 87                    .child(
 88                        h_flex()
 89                            .items_center()
 90                            .gap_1()
 91                            .child(
 92                                Checkbox::new("dont-show-again", ToggleState::Unselected).on_click(
 93                                    move |_, window, cx| {
 94                                        (self.on_dont_show_again)(true, window, cx);
 95                                    },
 96                                ),
 97                            )
 98                            .child(
 99                                Label::new("Don't show again")
100                                    .color(Color::Muted)
101                                    .size(ui::LabelSize::Small),
102                            ),
103                    )
104                    .child(
105                        h_flex()
106                            .gap_2()
107                            .child(
108                                Button::new("dismiss-button", "No Thanks")
109                                    .style(ButtonStyle::Subtle)
110                                    .on_click(self.on_dismiss),
111                            )
112                            .child(
113                                Button::new("cta-button", self.cta_text)
114                                    .style(ButtonStyle::Filled)
115                                    .on_click(self.on_click),
116                            ),
117                    ),
118            )
119    }
120}
121
122impl Component for Upsell {
123    fn scope() -> ComponentScope {
124        ComponentScope::Agent
125    }
126
127    fn name() -> &'static str {
128        "Upsell"
129    }
130
131    fn description() -> Option<&'static str> {
132        Some("A promotional component that displays a message with a call-to-action.")
133    }
134
135    fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
136        let examples = vec![
137            single_example(
138                "Default",
139                Upsell::new(
140                    "Upgrade to Zed Pro",
141                    "Get unlimited access to AI features and more with Zed Pro. Unlock advanced AI capabilities and other premium features.",
142                    "Upgrade Now",
143                    Box::new(|_, _, _| {}),
144                    Box::new(|_, _, _| {}),
145                    Box::new(|_, _, _| {}),
146                ).render(window, cx).into_any_element(),
147            ),
148            single_example(
149                "Short Message",
150                Upsell::new(
151                    "Try Zed Pro for free",
152                    "Start your 7-day trial today.",
153                    "Start Trial",
154                    Box::new(|_, _, _| {}),
155                    Box::new(|_, _, _| {}),
156                    Box::new(|_, _, _| {}),
157                ).render(window, cx).into_any_element(),
158            ),
159        ];
160
161        Some(v_flex().gap_4().children(examples).into_any_element())
162    }
163}