Add student plan (#47866)

Neel created

Release Notes:

- N/A

Change summary

crates/agent_ui/src/agent_configuration.rs   |  1 +
crates/ai_onboarding/src/ai_onboarding.rs    | 15 +++++++++++++++
crates/ai_onboarding/src/ai_upsell_card.rs   |  9 +++++++++
crates/ai_onboarding/src/plan_definitions.rs |  9 +++++++++
crates/cloud_api_types/src/plan.rs           |  4 ++++
crates/title_bar/src/title_bar.rs            |  1 +
6 files changed, 39 insertions(+)

Detailed changes

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -501,6 +501,7 @@ impl AgentConfiguration {
                 Plan::ZedFree => ("Free", Color::Default, free_chip_bg),
                 Plan::ZedProTrial => ("Pro Trial", Color::Accent, pro_chip_bg),
                 Plan::ZedPro => ("Pro", Color::Accent, pro_chip_bg),
+                Plan::ZedStudent => ("Student", Color::Accent, pro_chip_bg),
             };
 
             Chip::new(plan_name.to_string())

crates/ai_onboarding/src/ai_onboarding.rs 🔗

@@ -265,6 +265,20 @@ impl ZedAiOnboarding {
             .children(self.render_dismiss_button())
             .into_any_element()
     }
+
+    fn render_student_plan_state(&self, _cx: &mut App) -> AnyElement {
+        v_flex()
+            .gap_1()
+            .child(Headline::new("Welcome to Zed Student"))
+            .child(
+                Label::new("Here's what you get:")
+                    .color(Color::Muted)
+                    .mb_2(),
+            )
+            .child(PlanDefinitions.student_plan())
+            .children(self.render_dismiss_button())
+            .into_any_element()
+    }
 }
 
 impl RenderOnce for ZedAiOnboarding {
@@ -275,6 +289,7 @@ impl RenderOnce for ZedAiOnboarding {
                 Some(Plan::ZedFree) => self.render_free_plan_state(cx),
                 Some(Plan::ZedProTrial) => self.render_trial_state(cx),
                 Some(Plan::ZedPro) => self.render_pro_plan_state(cx),
+                Some(Plan::ZedStudent) => self.render_student_plan_state(cx),
             }
         } else {
             self.render_sign_in_disclaimer(cx)

crates/ai_onboarding/src/ai_upsell_card.rs 🔗

@@ -250,6 +250,15 @@ impl RenderOnce for AiUpsellCard {
                             .mb_2(),
                     )
                     .child(PlanDefinitions.pro_plan()),
+                Some(Plan::ZedStudent) => card
+                    .child(certified_user_stamp)
+                    .child(Label::new("You're in the Zed Student plan").size(LabelSize::Large))
+                    .child(
+                        Label::new("Here's what you get:")
+                            .color(Color::Muted)
+                            .mb_2(),
+                    )
+                    .child(PlanDefinitions.student_plan()),
             },
             // Signed Out State
             _ => card

crates/ai_onboarding/src/plan_definitions.rs 🔗

@@ -35,4 +35,13 @@ impl PlanDefinitions {
             .child(ListBulletItem::new("$5 of tokens"))
             .child(ListBulletItem::new("Usage-based billing beyond $5"))
     }
+
+    pub fn student_plan(&self) -> impl IntoElement {
+        List::new()
+            .child(ListBulletItem::new("Unlimited edit predictions"))
+            .child(ListBulletItem::new("$10 of tokens"))
+            .child(ListBulletItem::new(
+                "Optional credit packs for additional usage",
+            ))
+    }
 }

crates/cloud_api_types/src/plan.rs 🔗

@@ -9,6 +9,7 @@ pub enum Plan {
     ZedFree,
     ZedPro,
     ZedProTrial,
+    ZedStudent,
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
@@ -58,5 +59,8 @@ mod tests {
 
         let plan = serde_json::from_value::<Plan>(json!("zed_pro_trial")).unwrap();
         assert_eq!(plan, Plan::ZedProTrial);
+
+        let plan = serde_json::from_value::<Plan>(json!("zed_student")).unwrap();
+        assert_eq!(plan, Plan::ZedStudent);
     }
 }

crates/title_bar/src/title_bar.rs 🔗

@@ -965,6 +965,7 @@ impl TitleBar {
                         None | Some(Plan::ZedFree) => ("Free", Color::Default, free_chip_bg),
                         Some(Plan::ZedProTrial) => ("Pro Trial", Color::Accent, pro_chip_bg),
                         Some(Plan::ZedPro) => ("Pro", Color::Accent, pro_chip_bg),
+                        Some(Plan::ZedStudent) => ("Student", Color::Accent, pro_chip_bg),
                     };
 
                     menu.when(is_signed_in, |this| {