app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 4a751f6dcb695283dd73a22a4e1a4f39592d6ca3
parent 7457c7df6c33f8c714d20dd7d5ff6f398d9c91f6
Author: triesap <tyson@radroots.org>
Date:   Fri,  6 Feb 2026 18:08:48 +0000

app: update individual config step

- move individual name and location to profile step
- switch products input to multiline textarea
- parse products list from text input values
- build individual config using profile fields

Diffstat:
Mapp/src/app.rs | 120++++++++++++++++---------------------------------------------------------------
Mapp/src/config_flow.rs | 16++++++----------
2 files changed, 30 insertions(+), 106 deletions(-)

diff --git a/app/src/app.rs b/app/src/app.rs @@ -1552,8 +1552,6 @@ fn ConfigPage() -> impl IntoView { let farmer_location = RwSignal::new_local(String::new()); let farmer_products = RwSignal::new_local(Vec::<String>::new()); let farmer_products_input = RwSignal::new_local(String::new()); - let individual_name = RwSignal::new_local(String::new()); - let individual_location = RwSignal::new_local(String::new()); let individual_products = RwSignal::new_local(Vec::<String>::new()); let individual_products_input = RwSignal::new_local(String::new()); let business_name = RwSignal::new_local(String::new()); @@ -1571,8 +1569,8 @@ fn ConfigPage() -> impl IntoView { farmer_farm_name: farmer_farm_name.get(), farmer_location: farmer_location.get(), farmer_products: farmer_products.get(), - individual_name: individual_name.get(), - individual_location: individual_location.get(), + individual_name: profile_name.get(), + individual_location: profile_location.get(), individual_products: individual_products.get(), business_name: business_name.get(), business_location: business_location.get(), @@ -1674,21 +1672,23 @@ fn ConfigPage() -> impl IntoView { farmer_products_input.set(String::new()); } }; - let add_individual_product = { + let update_individual_products = { let individual_products = individual_products.clone(); let individual_products_input = individual_products_input.clone(); - move || { - let entry = individual_products_input.get_untracked(); - let trimmed = entry.trim().to_string(); - if trimmed.is_empty() { - return; - } - individual_products.update(|items| { - if !items.iter().any(|item| item.eq_ignore_ascii_case(&trimmed)) { - items.push(trimmed.clone()); + move |value: String| { + individual_products_input.set(value.clone()); + let mut items = Vec::new(); + for entry in value.split(|c| c == ',' || c == '\n') { + let trimmed = entry.trim(); + if trimmed.is_empty() { + continue; } - }); - individual_products_input.set(String::new()); + if items.iter().any(|item: &String| item.eq_ignore_ascii_case(trimmed)) { + continue; + } + items.push(trimmed.to_string()); + } + individual_products.set(items); } }; Effect::new(move || { @@ -1795,23 +1795,7 @@ fn ConfigPage() -> impl IntoView { id="app-config-role" class="app-view app-view-enter flex flex-col w-full gap-5" > - <RadrootsAppUiFormField - label="Role".to_string() - id="app-config-role-select".to_string() - hint="Selected during setup".to_string() - > - <div - id="app-config-role-value" - class="input-base bg-ly1-focus text-sm font-semibold text-ly1-gl" - > - {move || match role.get() { - Some(RadrootsAppRole::Farm) => "Farmer", - Some(RadrootsAppRole::Business) => "Business", - Some(RadrootsAppRole::Individual) => "Individual", - None => "Role unavailable", - }} - </div> - </RadrootsAppUiFormField> + <div id="app-config-role-spacer"></div> {move || match role.get() { Some(RadrootsAppRole::Farm) => view! { <div @@ -1898,76 +1882,20 @@ fn ConfigPage() -> impl IntoView { class="flex flex-col gap-4" > <RadrootsAppUiFormField - label="Name".to_string() - id="app-config-individual-name-field".to_string() - > - <input - id="app-config-individual-name" - class="input-base" - type="text" - placeholder="Your name".to_string() - prop:value=move || individual_name.get() - on:input=move |ev| { - individual_name.set(event_target_value(&ev)); - } - /> - </RadrootsAppUiFormField> - <RadrootsAppUiFormField - label="Location".to_string() - id="app-config-individual-location-field".to_string() - > - <input - id="app-config-individual-location" - class="input-base" - type="text" - placeholder="City or region".to_string() - prop:value=move || individual_location.get() - on:input=move |ev| { - individual_location.set(event_target_value(&ev)); - } - /> - </RadrootsAppUiFormField> - <RadrootsAppUiFormField label="Products interested in".to_string() id="app-config-individual-products-field".to_string() - hint="Press enter to add items".to_string() + hint="Use commas or new lines".to_string() > - <input + <textarea id="app-config-individual-products-input" - class="input-base" - type="text" - placeholder="Add a product".to_string() + class="textarea-base min-h-[7.5rem]" + rows="4" + placeholder="Apples, tomatoes, fresh herbs".to_string() prop:value=move || individual_products_input.get() - on:keydown=move |ev: KeyboardEvent| { - if ev.key() == "Enter" || ev.key() == "," { - ev.prevent_default(); - add_individual_product(); - } - } on:input=move |ev| { - individual_products_input.set(event_target_value(&ev)); + update_individual_products(event_target_value(&ev)); } - /> - <RadrootsAppUiChips id="app-config-individual-products".to_string()> - <For - each=move || individual_products.get() - key=|value| value.clone() - children=move |value| { - let remove_value = value.clone(); - view! { - <RadrootsAppUiChip - label=value.clone() - active=true - on_click=Callback::new(move |_| { - individual_products.update(|items| { - items.retain(|item| item != &remove_value); - }); - }) - /> - } - } - /> - </RadrootsAppUiChips> + ></textarea> </RadrootsAppUiFormField> </div> }.into_any(), diff --git a/app/src/config_flow.rs b/app/src/config_flow.rs @@ -128,9 +128,7 @@ fn role_step_valid(draft: &RadrootsAppConfigFlowDraft) -> bool { && has_items(&draft.farmer_products) } Some(RadrootsAppRole::Individual) => { - has_text(&draft.individual_name) - && has_text(&draft.individual_location) - && has_items(&draft.individual_products) + has_items(&draft.individual_products) } Some(RadrootsAppRole::Business) => { has_text(&draft.business_name) @@ -182,7 +180,7 @@ pub fn app_config_flow_build_config( return None; } Some(RadrootsAppConfigData { - profile, + profile: profile.clone(), role, farmer: Some(RadrootsAppConfigFarmer { farm_name, @@ -195,20 +193,18 @@ pub fn app_config_flow_build_config( }) } RadrootsAppRole::Individual => { - let name = normalize_text(&draft.individual_name)?; - let location = normalize_text(&draft.individual_location)?; let products = normalize_items(&draft.individual_products); if products.is_empty() { return None; } Some(RadrootsAppConfigData { - profile, + profile: profile.clone(), role, farmer: None, business: None, individual: Some(RadrootsAppConfigIndividual { - name, - location, + name: profile.name.clone(), + location: profile.location.clone(), products_interested: products, }), preferences, @@ -219,7 +215,7 @@ pub fn app_config_flow_build_config( let location = normalize_text(&draft.business_location)?; let operations = normalize_text(&draft.business_operations)?; Some(RadrootsAppConfigData { - profile, + profile: profile.clone(), role, farmer: None, business: Some(RadrootsAppConfigBusiness {