commit 84276a63a0d04d42abd9e63daf771f0830aea7ac
parent d5aeab8922a05125620f81e80f331439a676ff4a
Author: triesap <tyson@radroots.org>
Date: Wed, 28 Jan 2026 15:16:33 +0000
app: Add setup profile confirmation
- Confirm missing profile names before advancing
- Gate profile step advance on user confirmation
- Add farmer setup step and transitions
- Render farmer yes/no selection view and state
Diffstat:
3 files changed, 137 insertions(+), 8 deletions(-)
diff --git a/app/src/app.rs b/app/src/app.rs
@@ -79,6 +79,12 @@ enum RadrootsAppSetupKeyChoice {
AddExisting,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum RadrootsAppSetupFarmerChoice {
+ Yes,
+ No,
+}
+
fn active_key_label(value: Option<String>) -> String {
let Some(value) = value else {
return "missing".to_string();
@@ -157,6 +163,8 @@ fn spawn_health_checks(
}
const APP_HEALTH_CHECK_DELAY_MS: u32 = 300;
+const APP_SETUP_NO_PROFILE_NAME_CONFIRM: &str =
+ "Your profile will be created without a name. You can change this later in Settings > Profile";
fn app_health_check_delay_ms() -> u32 {
APP_HEALTH_CHECK_DELAY_MS
@@ -209,6 +217,7 @@ fn SetupPage() -> impl IntoView {
let navigate_home = navigate.clone();
let setup_step = RwSignal::new_local(app_setup_step_default());
let setup_key_choice = RwSignal::new_local(None::<RadrootsAppSetupKeyChoice>);
+ let setup_farmer_choice = RwSignal::new_local(None::<RadrootsAppSetupFarmerChoice>);
let nostr_key_add = RwSignal::new_local(String::new());
let profile_name = RwSignal::new_local(String::new());
let profile_nip05 = RwSignal::new_local(true);
@@ -222,7 +231,27 @@ fn SetupPage() -> impl IntoView {
let advance_step: Callback<MouseEvent> = {
let setup_step = setup_step.clone();
let setup_key_choice = setup_key_choice.clone();
+ let profile_name = profile_name.clone();
Callback::new(move |_| {
+ let current_step = setup_step.get();
+ if matches!(current_step, RadrootsAppSetupStep::Profile) {
+ let profile_name = profile_name.get();
+ if profile_name.trim().is_empty() {
+ let setup_step = setup_step.clone();
+ spawn_local(async move {
+ let notifications = RadrootsAppNotifications::new(None);
+ let confirm = notifications
+ .confirm_message(APP_SETUP_NO_PROFILE_NAME_CONFIRM)
+ .await;
+ if confirm {
+ setup_step.set(RadrootsAppSetupStep::FarmerSetup);
+ }
+ });
+ return;
+ }
+ setup_step.set(RadrootsAppSetupStep::FarmerSetup);
+ return;
+ }
setup_step.update(|step| {
*step = match *step {
RadrootsAppSetupStep::Intro => RadrootsAppSetupStep::KeyChoice,
@@ -238,7 +267,8 @@ fn SetupPage() -> impl IntoView {
}
}
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::Profile,
- RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::Profile,
+ RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::FarmerSetup,
};
});
})
@@ -258,6 +288,7 @@ fn SetupPage() -> impl IntoView {
}
_ => RadrootsAppSetupStep::KeyChoice,
},
+ RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Profile,
};
setup_step.set(next_step);
if matches!(next_step, RadrootsAppSetupStep::Intro) {
@@ -503,6 +534,81 @@ fn SetupPage() -> impl IntoView {
</div>
</section>
}.into_any(),
+ RadrootsAppSetupStep::FarmerSetup => view! {
+ <section
+ id="app-setup-farmer"
+ class="app-view app-view-enter flex flex-col w-full px-6 pt-10 pb-16"
+ on:click=move |_| {
+ setup_farmer_choice.set(None);
+ }
+ >
+ <div
+ id="app-setup-farmer-body"
+ class="flex flex-1 w-full flex-col justify-center items-center"
+ >
+ <div
+ id="app-setup-farmer-card"
+ class="flex flex-col h-[16rem] w-full gap-10 justify-start items-center"
+ >
+ <div
+ id="app-setup-farmer-title"
+ class="flex flex-row w-full justify-center items-center"
+ >
+ <p class="font-sans font-[600] text-ly0-gl text-3xl">
+ "Setup for Farmer"
+ </p>
+ </div>
+ <div
+ id="app-setup-farmer-actions"
+ class="flex flex-col w-full gap-5 justify-center items-center"
+ >
+ <button
+ id="app-setup-farmer-yes"
+ type="button"
+ class=move || {
+ if setup_farmer_choice.get()
+ == Some(RadrootsAppSetupFarmerChoice::Yes)
+ {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch ly1-selected-press el-re"
+ } else {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch bg-ly1 el-re"
+ }
+ }
+ on:click=move |ev| {
+ ev.stop_propagation();
+ setup_farmer_choice.set(Some(RadrootsAppSetupFarmerChoice::Yes));
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ "Yes"
+ </span>
+ </button>
+ <button
+ id="app-setup-farmer-no"
+ type="button"
+ class=move || {
+ if setup_farmer_choice.get()
+ == Some(RadrootsAppSetupFarmerChoice::No)
+ {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch ly1-selected-press el-re"
+ } else {
+ "flex flex-col h-bold_button w-lo_ios0 ios1:w-lo_ios1 justify-center items-center rounded-touch bg-ly1 el-re"
+ }
+ }
+ on:click=move |ev| {
+ ev.stop_propagation();
+ setup_farmer_choice.set(Some(RadrootsAppSetupFarmerChoice::No));
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ "No"
+ </span>
+ </button>
+ </div>
+ </div>
+ </div>
+ </section>
+ }.into_any(),
}}
<footer
id="app-setup-actions"
@@ -510,8 +616,10 @@ fn SetupPage() -> impl IntoView {
>
{move || {
let step = setup_step.get();
- let continue_disabled = matches!(step, RadrootsAppSetupStep::KeyChoice)
- && setup_key_choice.get().is_none();
+ let continue_disabled = (matches!(step, RadrootsAppSetupStep::KeyChoice)
+ && setup_key_choice.get().is_none())
+ || (matches!(step, RadrootsAppSetupStep::FarmerSetup)
+ && setup_farmer_choice.get().is_none());
let continue_action = RadrootsAppUiButtonLayoutAction {
label: "Continue".to_string(),
disabled: continue_disabled,
diff --git a/app/src/notifications.rs b/app/src/notifications.rs
@@ -3,6 +3,7 @@
use radroots_app_core::notifications::{
RadrootsClientNotifications,
RadrootsClientNotificationsConfig,
+ RadrootsClientNotificationsDialogConfirmOpts,
RadrootsClientNotificationsError,
RadrootsClientNotificationsPermission,
RadrootsClientWebNotifications,
@@ -121,6 +122,14 @@ impl RadrootsAppNotifications {
}
result
}
+
+ pub async fn confirm_message(&self, message: &str) -> bool {
+ self.client
+ .confirm(RadrootsClientNotificationsDialogConfirmOpts::Message(
+ message.to_string(),
+ ))
+ .await
+ }
}
#[cfg(test)]
diff --git a/app/src/setup.rs b/app/src/setup.rs
@@ -48,6 +48,7 @@ pub enum RadrootsAppSetupStep {
KeyChoice,
KeyAddExisting,
Profile,
+ FarmerSetup,
}
impl RadrootsAppSetupStep {
@@ -56,7 +57,8 @@ impl RadrootsAppSetupStep {
RadrootsAppSetupStep::Intro => RadrootsAppSetupStep::KeyChoice,
RadrootsAppSetupStep::KeyChoice => RadrootsAppSetupStep::KeyAddExisting,
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::Profile,
- RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::Profile,
+ RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::FarmerSetup,
}
}
@@ -66,11 +68,12 @@ impl RadrootsAppSetupStep {
RadrootsAppSetupStep::KeyChoice => RadrootsAppSetupStep::Intro,
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::KeyChoice,
RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::KeyAddExisting,
+ RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Profile,
}
}
pub const fn is_terminal(self) -> bool {
- matches!(self, RadrootsAppSetupStep::Profile)
+ matches!(self, RadrootsAppSetupStep::FarmerSetup)
}
}
@@ -333,7 +336,11 @@ mod tests {
);
assert_eq!(
RadrootsAppSetupStep::Profile.next(),
- RadrootsAppSetupStep::Profile
+ RadrootsAppSetupStep::FarmerSetup
+ );
+ assert_eq!(
+ RadrootsAppSetupStep::FarmerSetup.next(),
+ RadrootsAppSetupStep::FarmerSetup
);
}
@@ -355,14 +362,19 @@ mod tests {
RadrootsAppSetupStep::Profile.prev(),
RadrootsAppSetupStep::KeyAddExisting
);
+ assert_eq!(
+ RadrootsAppSetupStep::FarmerSetup.prev(),
+ RadrootsAppSetupStep::Profile
+ );
}
#[test]
- fn setup_step_terminal_matches_profile() {
+ fn setup_step_terminal_matches_farmer_setup() {
assert!(!RadrootsAppSetupStep::Intro.is_terminal());
assert!(!RadrootsAppSetupStep::KeyChoice.is_terminal());
assert!(!RadrootsAppSetupStep::KeyAddExisting.is_terminal());
- assert!(RadrootsAppSetupStep::Profile.is_terminal());
+ assert!(!RadrootsAppSetupStep::Profile.is_terminal());
+ assert!(RadrootsAppSetupStep::FarmerSetup.is_terminal());
}
#[test]