commit 68865cead977121c6f81f2bd6c27ebddde5bc785
parent 980f478bcb207acaafd29d8c8ce028c357a94991
Author: triesap <tyson@radroots.org>
Date: Mon, 2 Feb 2026 18:13:47 +0000
app: extend setup roles and eula ui
- add business setup step and role mapping
- expand role enum to individual farm business
- refresh eula actions layout and styles
- regenerate i18n build artifacts
Diffstat:
12 files changed, 312 insertions(+), 96 deletions(-)
diff --git a/app/i18n/build/i18n.catalog.json b/app/i18n/build/i18n.catalog.json
@@ -1248,6 +1248,17 @@
}
},
{
+ "key": "app.setup.business.title",
+ "id": 1801429027,
+ "args": [],
+ "features": {
+ "select": false,
+ "plural_cardinal": false,
+ "plural_ordinal": false,
+ "formatters": []
+ }
+ },
+ {
"key": "app.setup.eula.acceptance.body",
"id": 2382792662,
"args": [],
diff --git a/app/i18n/build/id_map.json b/app/i18n/build/id_map.json
@@ -112,6 +112,7 @@
"app.settings.appearance.color_mode.option.system": 116103587,
"app.settings.appearance.title": 410103833,
"app.settings.title": 2068161917,
+ "app.setup.business.title": 1801429027,
"app.setup.eula.acceptance.body": 2382792662,
"app.setup.eula.acceptance.title": 3507789532,
"app.setup.eula.changes.body": 951360382,
diff --git a/app/i18n/build/id_map_hash b/app/i18n/build/id_map_hash
@@ -1 +1 @@
-sha256:8fcc5219c69f901ff56bf3890c0127b9a7a3e324f1c4475efed91fd28a54bf64
+sha256:1e03c508c7169c9adae1a42d2067051774c96ed67e7396703a0ec3e56173a014
diff --git a/app/i18n/build/manifest.json b/app/i18n/build/manifest.json
@@ -1 +1 @@
-{"schema":1,"release_id":"dev","generated_at":"2026-02-02T00:00:00Z","default_locale":"en","supported_locales":["en"],"id_map_hash":"sha256:8fcc5219c69f901ff56bf3890c0127b9a7a3e324f1c4475efed91fd28a54bf64","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:4c050faaa27e4d6ed0203d1da1ecf344fa70eab12a747935ad918f752c3c930b","size":10709,"content_encoding":"identity","pack_schema":0}}}
-\ No newline at end of file
+{"schema":1,"release_id":"dev","generated_at":"2026-02-02T00:00:00Z","default_locale":"en","supported_locales":["en"],"id_map_hash":"sha256:1e03c508c7169c9adae1a42d2067051774c96ed67e7396703a0ec3e56173a014","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:e78181c9838bd04d3863f9e047a1705d885ad3faab2d3d64893fa479f52ac8e3","size":10765,"content_encoding":"identity","pack_schema":0}}}
+\ No newline at end of file
diff --git a/app/i18n/build/packs/en.mf2pack b/app/i18n/build/packs/en.mf2pack
Binary files differ.
diff --git a/app/i18n/locales/en/messages.mf2 b/app/i18n/locales/en/messages.mf2
@@ -148,6 +148,8 @@ app.setup.profile.confirm_no_name = Your profile will be created without a name.
app.setup.farmer.title = Setup for Farmer
+app.setup.business.title = Setup for Business
+
# eula
app.setup.eula.title = End User License Agreement
diff --git a/app/src/app.rs b/app/src/app.rs
@@ -42,7 +42,6 @@ use crate::{
app_config_default,
app_datastore_clear_setup_draft,
app_datastore_read_state,
- app_datastore_read_setup_draft,
app_datastore_write_profile_seed,
app_datastore_write_setup_draft,
app_keystore_nostr_ensure_key,
@@ -175,6 +174,25 @@ enum RadrootsAppSetupFarmerChoice {
No,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum RadrootsAppSetupBusinessChoice {
+ Yes,
+ No,
+}
+
+fn setup_role_from_choices(
+ farmer_choice: Option<RadrootsAppSetupFarmerChoice>,
+ business_choice: Option<RadrootsAppSetupBusinessChoice>,
+) -> Option<RadrootsAppRole> {
+ match farmer_choice? {
+ RadrootsAppSetupFarmerChoice::Yes => Some(RadrootsAppRole::Farm),
+ RadrootsAppSetupFarmerChoice::No => match business_choice? {
+ RadrootsAppSetupBusinessChoice::Yes => Some(RadrootsAppRole::Business),
+ RadrootsAppSetupBusinessChoice::No => Some(RadrootsAppRole::Individual),
+ },
+ }
+}
+
fn active_key_label(value: Option<String>) -> String {
let Some(value) = value else {
return t!("app.common.missing");
@@ -310,7 +328,9 @@ fn SetupPage() -> impl IntoView {
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 setup_business_choice = RwSignal::new_local(None::<RadrootsAppSetupBusinessChoice>);
let setup_eula_scrolled = RwSignal::new_local(false);
+ let setup_eula_scroll_ref: NodeRef<leptos::html::Div> = NodeRef::new();
let nostr_key_add = RwSignal::new_local(String::new());
let profile_name = RwSignal::new_local(String::new());
let profile_nip05 = RwSignal::new_local(true);
@@ -326,6 +346,8 @@ fn SetupPage() -> impl IntoView {
let backends = backends.clone();
let setup_draft_loaded = setup_draft_loaded.clone();
let setup_key_choice = setup_key_choice.clone();
+ let setup_farmer_choice = setup_farmer_choice.clone();
+ let setup_business_choice = setup_business_choice.clone();
let nostr_key_add = nostr_key_add.clone();
let profile_name = profile_name.clone();
let profile_nip05 = profile_nip05.clone();
@@ -338,20 +360,15 @@ fn SetupPage() -> impl IntoView {
else {
return;
};
+ setup_draft_loaded.set(true);
+ setup_key_choice.set(None);
+ setup_farmer_choice.set(None);
+ setup_business_choice.set(None);
+ nostr_key_add.set(String::new());
+ profile_name.set(String::new());
+ profile_nip05.set(true);
spawn_local(async move {
- if let Ok(Some(draft)) = app_datastore_read_setup_draft(datastore.as_ref(), &key_maps).await {
- if let Some(public_key) = draft.nostr_public_key {
- nostr_key_add.set(public_key);
- setup_key_choice.set(Some(RadrootsAppSetupKeyChoice::AddExisting));
- }
- if let Some(name) = draft.profile_name {
- profile_name.set(name);
- }
- if let Some(nip05_request) = draft.nip05_request {
- profile_nip05.set(nip05_request);
- }
- }
- setup_draft_loaded.set(true);
+ let _ = app_datastore_clear_setup_draft(datastore.as_ref(), &key_maps).await;
});
}
});
@@ -360,6 +377,7 @@ fn SetupPage() -> impl IntoView {
let setup_draft_loaded = setup_draft_loaded.clone();
let setup_key_choice = setup_key_choice.clone();
let setup_farmer_choice = setup_farmer_choice.clone();
+ let setup_business_choice = setup_business_choice.clone();
let nostr_key_add = nostr_key_add.clone();
let profile_name = profile_name.clone();
let profile_nip05 = profile_nip05.clone();
@@ -390,10 +408,14 @@ fn SetupPage() -> impl IntoView {
} else {
Some(profile_value)
};
+ let role = setup_role_from_choices(
+ setup_farmer_choice.get(),
+ setup_business_choice.get(),
+ );
let draft = RadrootsAppSetupDraft {
nostr_public_key,
profile_name,
- role: setup_farmer_choice.get().map(|_| RadrootsAppRole::default()),
+ role,
nip05_request: Some(profile_nip05.get()),
};
spawn_local(async move {
@@ -405,6 +427,8 @@ fn SetupPage() -> impl IntoView {
let backends = backends.clone();
let setup_step = setup_step.clone();
let setup_key_choice = setup_key_choice.clone();
+ let setup_farmer_choice = setup_farmer_choice.clone();
+ let setup_business_choice = setup_business_choice.clone();
let nostr_key_add = nostr_key_add.clone();
let profile_name = profile_name.clone();
let setup_required = setup_required.clone();
@@ -412,6 +436,11 @@ fn SetupPage() -> impl IntoView {
let current_step = setup_step.get();
if matches!(current_step, RadrootsAppSetupStep::Eula) {
let key_choice = setup_key_choice.get();
+ let setup_role = setup_role_from_choices(
+ setup_farmer_choice.get(),
+ setup_business_choice.get(),
+ )
+ .unwrap_or_else(RadrootsAppRole::default);
let nostr_key_add = nostr_key_add.get();
let profile_name = profile_name.get();
let profile_nip05 = profile_nip05.get();
@@ -500,6 +529,7 @@ fn SetupPage() -> impl IntoView {
active_key,
eula_date,
nip05_key,
+ setup_role,
)
.await
{
@@ -513,6 +543,9 @@ fn SetupPage() -> impl IntoView {
}
if matches!(current_step, RadrootsAppSetupStep::Profile) {
let profile_name = profile_name.get();
+ if profile_nip05.get() && profile_name.trim().is_empty() {
+ return;
+ }
if profile_name.trim().is_empty() {
let setup_step = setup_step.clone();
let confirm_message = t!("app.setup.profile.confirm_no_name");
@@ -544,7 +577,12 @@ fn SetupPage() -> impl IntoView {
}
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::Profile,
RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::FarmerSetup,
- RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Eula,
+ RadrootsAppSetupStep::FarmerSetup => match setup_farmer_choice.get() {
+ Some(RadrootsAppSetupFarmerChoice::Yes) => RadrootsAppSetupStep::Eula,
+ Some(RadrootsAppSetupFarmerChoice::No) => RadrootsAppSetupStep::BusinessSetup,
+ None => RadrootsAppSetupStep::FarmerSetup,
+ },
+ RadrootsAppSetupStep::BusinessSetup => RadrootsAppSetupStep::Eula,
RadrootsAppSetupStep::Eula => RadrootsAppSetupStep::Eula,
};
});
@@ -559,6 +597,7 @@ fn SetupPage() -> impl IntoView {
let rewind_step: Callback<MouseEvent> = {
let setup_step = setup_step.clone();
let setup_key_choice = setup_key_choice.clone();
+ let setup_farmer_choice = setup_farmer_choice.clone();
Callback::new(move |_| {
let current_step = setup_step.get();
let next_step = match current_step {
@@ -572,7 +611,11 @@ fn SetupPage() -> impl IntoView {
_ => RadrootsAppSetupStep::KeyChoice,
},
RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Profile,
- RadrootsAppSetupStep::Eula => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::BusinessSetup => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::Eula => match setup_farmer_choice.get() {
+ Some(RadrootsAppSetupFarmerChoice::No) => RadrootsAppSetupStep::BusinessSetup,
+ _ => RadrootsAppSetupStep::FarmerSetup,
+ },
};
setup_step.set(next_step);
if matches!(next_step, RadrootsAppSetupStep::Intro) {
@@ -591,6 +634,22 @@ fn SetupPage() -> impl IntoView {
}
}
});
+ Effect::new({
+ let setup_step = setup_step.clone();
+ let setup_eula_scrolled = setup_eula_scrolled.clone();
+ let setup_eula_scroll_ref = setup_eula_scroll_ref.clone();
+ move |_| {
+ if !matches!(setup_step.get(), RadrootsAppSetupStep::Eula) {
+ return;
+ }
+ let Some(target) = setup_eula_scroll_ref.get() else {
+ return;
+ };
+ if target.scroll_height() <= target.client_height() {
+ setup_eula_scrolled.set(true);
+ }
+ }
+ });
view! {
<main
id="app-setup"
@@ -910,6 +969,81 @@ fn SetupPage() -> impl IntoView {
</div>
</section>
}.into_any(),
+ RadrootsAppSetupStep::BusinessSetup => view! {
+ <section
+ id="app-setup-business"
+ class="app-view app-view-enter flex flex-col w-full px-6 pt-10 pb-16"
+ on:click=move |_| {
+ setup_business_choice.set(None);
+ }
+ >
+ <div
+ id="app-setup-business-body"
+ class="flex flex-1 w-full flex-col justify-center items-center"
+ >
+ <div
+ id="app-setup-business-card"
+ class="flex flex-col h-[16rem] w-full gap-10 justify-start items-center"
+ >
+ <div
+ id="app-setup-business-title"
+ class="flex flex-row w-full justify-center items-center"
+ >
+ <p class="font-sans font-[600] text-ly0-gl text-3xl">
+ {t!("app.setup.business.title")}
+ </p>
+ </div>
+ <div
+ id="app-setup-business-actions"
+ class="flex flex-col w-full gap-5 justify-center items-center"
+ >
+ <button
+ id="app-setup-business-yes"
+ type="button"
+ class=move || {
+ if setup_business_choice.get()
+ == Some(RadrootsAppSetupBusinessChoice::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_business_choice.set(Some(RadrootsAppSetupBusinessChoice::Yes));
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ {t!("app.common.yes")}
+ </span>
+ </button>
+ <button
+ id="app-setup-business-no"
+ type="button"
+ class=move || {
+ if setup_business_choice.get()
+ == Some(RadrootsAppSetupBusinessChoice::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_business_choice.set(Some(RadrootsAppSetupBusinessChoice::No));
+ }
+ >
+ <span class="font-sans font-[600] text-ly0-gl text-xl">
+ {t!("app.common.no")}
+ </span>
+ </button>
+ </div>
+ </div>
+ </div>
+ </section>
+ }.into_any(),
RadrootsAppSetupStep::Eula => view! {
<section
id="app-setup-eula"
@@ -929,7 +1063,8 @@ fn SetupPage() -> impl IntoView {
</header>
<div
id="app-setup-eula-scroll"
- class="app-page-scroll scroll-hide flex flex-col flex-1 min-h-0 w-full gap-6 px-1 pb-6 overscroll-contain"
+ class="app-page-scroll scroll-hide flex flex-col flex-1 min-h-0 w-full gap-6 px-1 pb-20 se-compact:pb-12 overscroll-contain font-mono"
+ node_ref=setup_eula_scroll_ref
on:scroll=move |ev| {
if setup_eula_scrolled.get() {
return;
@@ -938,7 +1073,7 @@ fn SetupPage() -> impl IntoView {
let scroll_top = target.scroll_top();
let scroll_height = target.scroll_height();
let client_height = target.client_height();
- if scroll_top + client_height >= scroll_height {
+ if scroll_top + client_height + 1 >= scroll_height {
setup_eula_scrolled.set(true);
}
}
@@ -1046,59 +1181,33 @@ fn SetupPage() -> impl IntoView {
</div>
<div
id="app-setup-eula-actions"
- class="flex flex-row w-full pt-4 justify-center items-center"
+ class="flex flex-col w-full pt-4 pb-2 justify-center items-center"
>
- <button
- type="button"
- class=move || {
- if setup_eula_scrolled.get() {
- "group flex flex-row basis-1/2 gap-3 justify-center items-center"
- } else {
- "group flex flex-row basis-1/2 gap-3 justify-center items-center opacity-80"
- }
- }
- on:click=move |ev| {
- ev.stop_propagation();
- rewind_step.run(ev);
- }
- >
- <span class="font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl/80 el-re">
- "-"
- </span>
- <span class="font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl/80 el-re">
- {t!("app.common.disagree")}
- </span>
- <span class="font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl/80 el-re">
- "-"
- </span>
- </button>
- <button
- type="button"
- aria-disabled=move || !setup_eula_scrolled.get()
- class=move || {
- if setup_eula_scrolled.get() {
- "relative group flex flex-row basis-1/2 gap-3 justify-center items-center el-re"
- } else {
- "relative group flex flex-row basis-1/2 gap-3 justify-center items-center opacity-40 pointer-events-none"
- }
- }
- on:click=move |ev| {
- ev.stop_propagation();
- if setup_eula_scrolled.get() {
- advance_step.run(());
- }
- }
- >
- <span class="font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re">
- "-"
- </span>
- <span class="font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re">
- {t!("app.common.agree")}
- </span>
- <span class="font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re">
- "-"
- </span>
- </button>
+ {move || {
+ let continue_action = RadrootsAppUiButtonLayoutAction {
+ label: t!("app.common.agree"),
+ disabled: !setup_eula_scrolled.get(),
+ loading: false,
+ on_click: advance_step_click.clone(),
+ class: Some("button-layout-accent button-layout-compact".to_string()),
+ class_label: Some("text-base".to_string()),
+ style: None,
+ };
+ let back_action = RadrootsAppUiButtonLayoutBackAction {
+ visible: true,
+ label: Some(t!("app.common.disagree")),
+ disabled: false,
+ on_click: rewind_step.clone(),
+ compact: true,
+ };
+ view! {
+ <RadrootsAppUiButtonLayoutPair
+ continue_action=continue_action
+ back=back_action
+ class="gap-2".to_string()
+ />
+ }.into_any()
+ }}
</div>
</section>
}.into_any(),
@@ -1115,7 +1224,12 @@ fn SetupPage() -> impl IntoView {
let continue_disabled = (matches!(step, RadrootsAppSetupStep::KeyChoice)
&& setup_key_choice.get().is_none())
|| (matches!(step, RadrootsAppSetupStep::FarmerSetup)
- && setup_farmer_choice.get().is_none());
+ && setup_farmer_choice.get().is_none())
+ || (matches!(step, RadrootsAppSetupStep::BusinessSetup)
+ && setup_business_choice.get().is_none())
+ || (matches!(step, RadrootsAppSetupStep::Profile)
+ && profile_nip05.get()
+ && profile_name.get().trim().is_empty());
let continue_label = t!("app.common.continue");
let back_label = t!("app.common.back");
let continue_action = RadrootsAppUiButtonLayoutAction {
@@ -1123,12 +1237,16 @@ fn SetupPage() -> impl IntoView {
disabled: continue_disabled,
loading: false,
on_click: advance_step_click.clone(),
+ class: None,
+ class_label: None,
+ style: None,
};
let back_action = RadrootsAppUiButtonLayoutBackAction {
visible: !matches!(step, RadrootsAppSetupStep::Intro),
label: Some(back_label),
disabled: false,
on_click: rewind_step.clone(),
+ compact: false,
};
view! {
<RadrootsAppUiButtonLayoutPair
diff --git a/app/src/bootstrap.rs b/app/src/bootstrap.rs
@@ -15,6 +15,7 @@ use crate::{
app_state_record_validate,
app_state_timestamp_ms,
RadrootsAppProfileSeed,
+ RadrootsAppRole,
RadrootsAppState,
RadrootsAppSetupDraft,
RadrootsAppStateError,
@@ -85,7 +86,7 @@ async fn app_datastore_migrate_legacy_state<T: RadrootsClientDatastore>(
Ok(value) => value,
Err(_) => return Ok(None),
};
- let state = app_setup_state_new(active_key.clone(), eula_date);
+ let state = app_setup_state_new(active_key.clone(), eula_date, RadrootsAppRole::default());
let record = app_state_record_new(state, 1, app_state_timestamp_ms());
let stored = app_datastore_write_state_record(datastore, key_maps, &record).await?;
let _ = datastore.del(key_nostr).await;
@@ -1020,7 +1021,7 @@ mod tests {
let draft = RadrootsAppSetupDraft {
nostr_public_key: Some("pub".to_string()),
profile_name: Some("radroots".to_string()),
- role: Some(RadrootsAppRole::Public),
+ role: Some(RadrootsAppRole::Individual),
nip05_request: Some(true),
};
let stored = futures::executor::block_on(app_datastore_write_setup_draft(
diff --git a/app/src/data.rs b/app/src/data.rs
@@ -6,12 +6,17 @@ use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RadrootsAppRole {
- Public,
+ #[serde(rename = "individual", alias = "Public", alias = "public", alias = "Individual")]
+ Individual,
+ #[serde(rename = "farm")]
+ Farm,
+ #[serde(rename = "business")]
+ Business,
}
impl Default for RadrootsAppRole {
fn default() -> Self {
- RadrootsAppRole::Public
+ RadrootsAppRole::Individual
}
}
@@ -188,15 +193,15 @@ mod tests {
};
#[test]
- fn role_defaults_to_public() {
- assert_eq!(RadrootsAppRole::default(), RadrootsAppRole::Public);
+ fn role_defaults_to_individual() {
+ assert_eq!(RadrootsAppRole::default(), RadrootsAppRole::Individual);
}
#[test]
fn state_defaults_empty() {
let data = RadrootsAppState::default();
assert_eq!(data.active_key, "");
- assert_eq!(data.role, RadrootsAppRole::Public);
+ assert_eq!(data.role, RadrootsAppRole::Individual);
assert_eq!(data.eula_date, "");
assert_eq!(data.eula_version, "0.1.0");
assert_eq!(data.eula_hash, "unknown");
diff --git a/app/src/setup.rs b/app/src/setup.rs
@@ -34,10 +34,14 @@ pub fn app_setup_eula_date() -> String {
Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)
}
-pub fn app_setup_state_new(active_key: String, eula_date: String) -> RadrootsAppState {
+pub fn app_setup_state_new(
+ active_key: String,
+ eula_date: String,
+ role: RadrootsAppRole,
+) -> RadrootsAppState {
RadrootsAppState {
active_key,
- role: RadrootsAppRole::default(),
+ role,
eula_date,
eula_version: String::from("0.1.0"),
eula_hash: String::from("unknown"),
@@ -54,6 +58,7 @@ pub enum RadrootsAppSetupStep {
KeyAddExisting,
Profile,
FarmerSetup,
+ BusinessSetup,
Eula,
}
@@ -64,7 +69,8 @@ impl RadrootsAppSetupStep {
RadrootsAppSetupStep::KeyChoice => RadrootsAppSetupStep::KeyAddExisting,
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::Profile,
RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::FarmerSetup,
- RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Eula,
+ RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::BusinessSetup,
+ RadrootsAppSetupStep::BusinessSetup => RadrootsAppSetupStep::Eula,
RadrootsAppSetupStep::Eula => RadrootsAppSetupStep::Eula,
}
}
@@ -76,7 +82,8 @@ impl RadrootsAppSetupStep {
RadrootsAppSetupStep::KeyAddExisting => RadrootsAppSetupStep::KeyChoice,
RadrootsAppSetupStep::Profile => RadrootsAppSetupStep::KeyAddExisting,
RadrootsAppSetupStep::FarmerSetup => RadrootsAppSetupStep::Profile,
- RadrootsAppSetupStep::Eula => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::BusinessSetup => RadrootsAppSetupStep::FarmerSetup,
+ RadrootsAppSetupStep::Eula => RadrootsAppSetupStep::BusinessSetup,
}
}
@@ -110,7 +117,15 @@ pub async fn app_setup_initialize<T: RadrootsClientDatastore, K: RadrootsClientK
RadrootsAppInitError::Keystore(RadrootsClientKeystoreError::NostrInvalidSecretKey)
}
})?;
- app_setup_finalize_with_key(datastore, key_maps, active_key, app_setup_eula_date(), None).await
+ app_setup_finalize_with_key(
+ datastore,
+ key_maps,
+ active_key,
+ app_setup_eula_date(),
+ None,
+ RadrootsAppRole::default(),
+ )
+ .await
}
pub async fn app_setup_finalize_with_key<T: RadrootsClientDatastore>(
@@ -119,8 +134,9 @@ pub async fn app_setup_finalize_with_key<T: RadrootsClientDatastore>(
active_key: String,
eula_date: String,
nip05_key: Option<String>,
+ role: RadrootsAppRole,
) -> RadrootsAppInitResult<RadrootsAppState> {
- let mut state = app_setup_state_new(active_key.clone(), eula_date);
+ let mut state = app_setup_state_new(active_key.clone(), eula_date, role);
state.nip05_key = nip05_key;
let stored_state = app_datastore_create_state(datastore, key_maps, &state).await?;
let key_name = app_datastore_key_nostr_key(key_maps).map_err(RadrootsAppInitError::Config)?;
@@ -334,9 +350,13 @@ mod tests {
#[test]
fn setup_state_new_populates_defaults() {
- let state = app_setup_state_new("pub".to_string(), "2025-01-01T00:00:00Z".to_string());
+ let state = app_setup_state_new(
+ "pub".to_string(),
+ "2025-01-01T00:00:00Z".to_string(),
+ RadrootsAppRole::default(),
+ );
assert_eq!(state.active_key, "pub");
- assert_eq!(state.role, RadrootsAppRole::Public);
+ assert_eq!(state.role, RadrootsAppRole::Individual);
assert_eq!(state.eula_date, "2025-01-01T00:00:00Z");
assert!(!state.relays.is_empty());
assert!(state.nip05_key.is_none());
@@ -374,6 +394,10 @@ mod tests {
);
assert_eq!(
RadrootsAppSetupStep::FarmerSetup.next(),
+ RadrootsAppSetupStep::BusinessSetup
+ );
+ assert_eq!(
+ RadrootsAppSetupStep::BusinessSetup.next(),
RadrootsAppSetupStep::Eula
);
assert_eq!(
@@ -405,9 +429,13 @@ mod tests {
RadrootsAppSetupStep::Profile
);
assert_eq!(
- RadrootsAppSetupStep::Eula.prev(),
+ RadrootsAppSetupStep::BusinessSetup.prev(),
RadrootsAppSetupStep::FarmerSetup
);
+ assert_eq!(
+ RadrootsAppSetupStep::Eula.prev(),
+ RadrootsAppSetupStep::BusinessSetup
+ );
}
#[test]
@@ -417,6 +445,7 @@ mod tests {
assert!(!RadrootsAppSetupStep::KeyAddExisting.is_terminal());
assert!(!RadrootsAppSetupStep::Profile.is_terminal());
assert!(!RadrootsAppSetupStep::FarmerSetup.is_terminal());
+ assert!(!RadrootsAppSetupStep::BusinessSetup.is_terminal());
assert!(RadrootsAppSetupStep::Eula.is_terminal());
}
@@ -462,6 +491,7 @@ mod tests {
"pub".to_string(),
"2025-01-01T00:00:00Z".to_string(),
None,
+ RadrootsAppRole::default(),
))
.expect("finalize");
assert_eq!(state.active_key, "pub");
diff --git a/app/stylesheets/apps-ui.css b/app/stylesheets/apps-ui.css
@@ -40,6 +40,36 @@
@apply font-sans font-[600] tracking-wide text-ly1-gl-shade group-active:text-ly1-gl/40 el-re;
}
+@layer utilities {
+ .button-layout-compact {
+ height: 48px;
+ min-height: 48px;
+ }
+
+ .button-layout-accent {
+ background: hsl(var(--ly0-gl-hl) / 1);
+ color: #fff;
+ }
+
+ .button-layout-accent:active {
+ background: hsl(var(--ly0-gl-hl) / 0.85);
+ }
+
+ .button-layout-accent:disabled {
+ opacity: 1;
+ background: hsl(var(--ly0-gl-hl) / 0.6);
+ color: #fff;
+ }
+
+ .button-layout-accent .button-layout-label {
+ color: #fff;
+ }
+
+ .button-layout-accent:active .button-layout-label {
+ color: rgba(255, 255, 255, 0.8);
+ }
+}
+
@utility input-base {
@apply flex w-full items-center rounded-touch border border-ly1-edge/60 bg-ly1 px-3 py-2 font-sans text-form_base text-ly1-gl placeholder:text-ly1-gl-label/70 transition-colors focus:outline-none focus:bg-ly1-focus focus:border-ly1-edge/80 disabled:opacity-60;
@apply focus:shadow-[inset_0_0_0_1px_hsl(var(--ly1-edge)/0.6)];
diff --git a/crates/ui-components/src/button_layout.rs b/crates/ui-components/src/button_layout.rs
@@ -28,6 +28,9 @@ pub struct RadrootsAppUiButtonLayoutAction {
pub disabled: bool,
pub loading: bool,
pub on_click: Callback<MouseEvent>,
+ pub class: Option<String>,
+ pub class_label: Option<String>,
+ pub style: Option<String>,
}
#[derive(Clone)]
@@ -36,6 +39,7 @@ pub struct RadrootsAppUiButtonLayoutBackAction {
pub label: Option<String>,
pub disabled: bool,
pub on_click: Callback<MouseEvent>,
+ pub compact: bool,
}
#[component]
@@ -46,6 +50,7 @@ pub fn RadrootsAppUiButtonLayout(
#[prop(optional)] loading: bool,
#[prop(optional)] class: Option<String>,
#[prop(optional)] class_label: Option<String>,
+ #[prop(optional)] style: Option<String>,
#[prop(optional)] hide_active: bool,
) -> impl IntoView {
let allow_active = !disabled && !hide_active;
@@ -67,6 +72,7 @@ pub fn RadrootsAppUiButtonLayout(
<button
type="button"
class=button_class
+ style=style
disabled=disabled
on:click=move |ev| {
ev.stop_propagation();
@@ -104,6 +110,9 @@ pub fn RadrootsAppUiButtonLayoutPair(
disabled=continue_action.disabled
loading=continue_action.loading
on_click=continue_action.on_click
+ class=continue_action.class.unwrap_or_default()
+ class_label=continue_action.class_label.unwrap_or_default()
+ style=continue_action.style.unwrap_or_default()
/>
{back.map(|back_action| {
view! {
@@ -113,15 +122,24 @@ pub fn RadrootsAppUiButtonLayoutPair(
let back_disabled = back_action.disabled;
let back_on_click = back_action.on_click.clone();
let back_visible = back_action.visible;
+ let back_compact = back_action.compact;
let back_text_class = radroots_app_ui_button_class_merge(&[
Some("font-sans font-[600] tracking-wide text-ly1-gl-shade"),
if back_disabled { None } else { Some("group-active:text-ly1-gl/40") },
]);
- let back_button_class = radroots_app_ui_button_class_merge(&[
- if back_disabled { None } else { Some("group") },
- Some("flex flex-row h-12 w-lo_ios0 ios1:w-lo_ios1 justify-center items-center -translate-y-[2px] transition-opacity duration-[160ms] ease-[cubic-bezier(.2,.8,.2,1)]"),
- if back_visible { Some("opacity-100") } else { Some("opacity-0 pointer-events-none") },
- ]);
+ let back_button_class = if back_compact {
+ radroots_app_ui_button_class_merge(&[
+ if back_disabled { None } else { Some("group") },
+ Some("flex flex-row w-fit justify-center items-center py-1 transition-opacity duration-[160ms] ease-[cubic-bezier(.2,.8,.2,1)]"),
+ if back_visible { Some("opacity-100") } else { Some("opacity-0 pointer-events-none") },
+ ])
+ } else {
+ radroots_app_ui_button_class_merge(&[
+ if back_disabled { None } else { Some("group") },
+ Some("flex flex-row h-12 w-lo_ios0 ios1:w-lo_ios1 justify-center items-center -translate-y-[2px] transition-opacity duration-[160ms] ease-[cubic-bezier(.2,.8,.2,1)]"),
+ if back_visible { Some("opacity-100") } else { Some("opacity-0 pointer-events-none") },
+ ])
+ };
view! {
<button
type="button"