diff --git a/src/components/molecules/action_request_list.rs b/src/components/molecules/action_request_list.rs index eacbbd7..2aa7eb2 100644 --- a/src/components/molecules/action_request_list.rs +++ b/src/components/molecules/action_request_list.rs @@ -1,12 +1,12 @@ -use dioxus::prelude::*; -use dioxus_std::{i18n::use_i18, translate}; use crate::{ components::atoms::ActionRequest, hooks::use_initiative::{ - ActionItem, AddMembersAction, ConvictionVote, KusamaTreasuryAction, VoteType, - VotingOpenGovAction, + ActionItem, AddMembersAction, CommunityTransferAction, ConvictionVote, + KusamaTreasuryAction, VoteType, VotingOpenGovAction, }, }; +use dioxus::prelude::*; +use dioxus_std::{i18n::use_i18, translate}; #[derive(PartialEq, Props, Clone)] pub struct ActionRequestListProps { actions: Vec, @@ -15,7 +15,7 @@ pub fn ActionRequestList(props: ActionRequestListProps) -> Element { let i18 = use_i18(); let render_add_members = |action: &AddMembersAction| { rsx!( - ActionRequest { name: "Add Members", details: action.members.len().to_string() } + ActionRequest { name: translate!(i18, "initiative.steps.actions.add_members.title"), details: action.members.len().to_string() } ul { class: "requests", for member in action.members.iter() { li { @@ -27,7 +27,7 @@ pub fn ActionRequestList(props: ActionRequestListProps) -> Element { }; let render_kusama_treasury = |action: &KusamaTreasuryAction| { rsx!( - ActionRequest { name: "Kusama Treasury Request" } + ActionRequest { name: translate!(i18, "initiative.steps.actions.kusama_treasury.title") } ul { class: "requests", for (index , period) in action.periods.iter().enumerate() { li { @@ -42,7 +42,7 @@ pub fn ActionRequestList(props: ActionRequestListProps) -> Element { }; let render_voting_open_gov = |action: &VotingOpenGovAction| { rsx!( - ActionRequest { name: "Voting Open Gov", details: action.proposals.len().to_string() } + ActionRequest { name: translate!(i18, "initiative.steps.actions.voting_open_gov.title"), details: action.proposals.len().to_string() } ul { class: "requests", for proposal in action.proposals.iter() { li { @@ -70,13 +70,29 @@ pub fn ActionRequestList(props: ActionRequestListProps) -> Element { } ) }; + let render_community_transfer = |action: &CommunityTransferAction| { + rsx!( + ActionRequest { name: translate!(i18, "initiative.steps.actions.community_transfer.title") } + ul { class: "requests", + for transfer in action.transfers.iter() { + li { + ActionRequest { + name: format!("{}", transfer.account), + details: format!("{} KSM", transfer.value as f64 / 1_000_000_000_000.0) + } + } + } + } + ) + }; rsx!( for request in props.actions.iter() { div { class: "requests", match request { ActionItem::AddMembers(action) => render_add_members(& action), ActionItem::KusamaTreasury(action) => render_kusama_treasury(& action), - ActionItem::VotingOpenGov(action) => render_voting_open_gov(& action) } + ActionItem::VotingOpenGov(action) => render_voting_open_gov(& action) , + ActionItem::CommunityTransfer(action) => render_community_transfer(& action) } } } ) diff --git a/src/components/molecules/actions/mod.rs b/src/components/molecules/actions/mod.rs index 3706919..bb1fad2 100644 --- a/src/components/molecules/actions/mod.rs +++ b/src/components/molecules/actions/mod.rs @@ -1,6 +1,8 @@ pub mod members; pub mod treasury; pub mod voting; +pub mod transfer; +pub use transfer::TransferAction; pub use members::MembersAction; pub use treasury::TreasuryAction; pub use voting::VotingAction; diff --git a/src/components/molecules/actions/transfer.rs b/src/components/molecules/actions/transfer.rs new file mode 100644 index 0000000..8b837fd --- /dev/null +++ b/src/components/molecules/actions/transfer.rs @@ -0,0 +1,113 @@ +use crate::{ + components::atoms::{ + dropdown::ElementSize, icon_button::Variant, AddPlus, Icon, IconButton, + Input, MinusCircle, + }, + hooks::use_initiative::{use_initiative, ActionItem, CommunityTransferAction, TransferItem}, +}; +use dioxus::prelude::*; +use dioxus_std::{i18n::use_i18, translate}; +#[derive(PartialEq, Props, Clone)] +pub struct VotingProps { + index: usize, + meta: CommunityTransferAction, +} +const KUSAMA_PRECISION_DECIMALS: u64 = 1_000_000_000_000; +pub fn TransferAction(props: VotingProps) -> Element { + let i18 = use_i18(); + let mut initiative = use_initiative(); + rsx!( + ul { class: "form__inputs form__inputs--combo", + { + props.meta.transfers.iter().enumerate().map(|(index_meta, transfer)| { + rsx!( + li { + div { + style: " + width: 100%; + display: flex; + gap: 4px; + ", + Input { + message: transfer.account.clone(), + size: ElementSize::Small, + placeholder: translate!(i18, "initiative.steps.actions.community_transfer.dest.placeholder"), + error: None, + on_input: move |event: Event| { + if let ActionItem::CommunityTransfer(ref mut meta) = initiative.get_action(props.index) { + meta.transfers[index_meta].account = event.value() ; + initiative.update_action(props.index, ActionItem::CommunityTransfer(meta.clone())); + } + }, + on_keypress: move |_| {}, + on_click: move |_| {}, + } + Input { + message: (transfer.value / KUSAMA_PRECISION_DECIMALS).to_string(), + size: ElementSize::Small, + placeholder: translate!(i18, "initiative.steps.actions.community_transfer.amount.placeholder"), + error: None, + right_text: { + rsx!( + span { class: "input--right__text", + "KSM" + } + ) + }, + on_input: move |event: Event| { + if let ActionItem::CommunityTransfer(ref mut meta) = initiative.get_action(props.index) { + // Scale amount + let amount = event.value().parse::().unwrap_or(0.0); + let scaled_amount = amount * KUSAMA_PRECISION_DECIMALS as f64; + meta.transfers[index_meta].value = scaled_amount as u64 ; + initiative.update_action(props.index, ActionItem::CommunityTransfer(meta.clone())); + } + }, + on_keypress: move |_| {}, + on_click: move |_| {}, + } + } + IconButton { + variant: Variant::Round, + size: ElementSize::Small, + class: "button--avatar", + body: rsx!( + Icon { + icon: MinusCircle, + height: 24, + width: 24, + fill: "var(--state-primary-active)" + } + ), + on_click: move |_| { + if let ActionItem::CommunityTransfer(ref mut meta) = initiative.get_action(props.index) { + meta.transfers.remove(index_meta); + initiative.update_action(props.index, ActionItem::CommunityTransfer(meta.clone())); + } + } + } + } + ) + }) + }, + IconButton { + variant: Variant::Round, + size: ElementSize::Small, + class: "button--avatar", + body: rsx!( + Icon { icon : AddPlus, height : 24, width : 24, fill : + "var(--state-primary-active)" } + ), + on_click: move |_| { + if let ActionItem::CommunityTransfer(ref mut meta) = initiative + .get_action(props.index) + { + meta.add_transfer(TransferItem::default()); + initiative + .update_action(props.index, ActionItem::CommunityTransfer(meta.clone())); + } + } + } + } + ) +} diff --git a/src/components/molecules/initiative/actions.rs b/src/components/molecules/initiative/actions.rs index 8ebe38f..5c2a8e5 100644 --- a/src/components/molecules/initiative/actions.rs +++ b/src/components/molecules/initiative/actions.rs @@ -6,7 +6,7 @@ use crate::{ dropdown::ElementSize, icon_button::Variant, AddPlus, Dropdown, Icon, IconButton, SubstractLine, }, - molecules::{MembersAction, TreasuryAction, VotingAction}, + molecules::{MembersAction, TransferAction, TreasuryAction, VotingAction}, }, hooks::use_initiative::{ use_initiative, ActionItem, AddMembersAction, MediumOptions, MemberItem, @@ -87,6 +87,14 @@ pub fn InitiativeActions() -> Element { } ) } + ActionItem::CommunityTransfer(meta) => { + rsx!( + TransferAction { + index: index, + meta: meta.clone() + } + ) + } } } ) diff --git a/src/hooks/use_initiative.rs b/src/hooks/use_initiative.rs index 5351403..1e00679 100644 --- a/src/hooks/use_initiative.rs +++ b/src/hooks/use_initiative.rs @@ -1,6 +1,6 @@ +use crate::components::atoms::dropdown::DropdownItem; use dioxus::prelude::*; use serde::{Deserialize, Serialize}; -use crate::components::atoms::dropdown::DropdownItem; #[derive(Clone, Default, Deserialize, Serialize, Debug)] pub struct InfoForm { pub name: String, @@ -154,43 +154,70 @@ impl VotingOpenGovAction { } } } +#[derive(PartialEq, Clone, Default, Deserialize, Serialize, Debug)] +pub struct TransferItem { + pub account: String, + pub value: u64 +} +pub type Transfers = Vec; +#[derive(PartialEq, Clone, Debug, Deserialize, Serialize, Default)] +pub struct CommunityTransferAction { + pub transfers: Transfers, +} +impl CommunityTransferAction { + pub fn add_transfer(&mut self, transfer: TransferItem) { + self.transfers.push(transfer); + } + pub fn update_transfer(&mut self, index: usize, transfer: TransferItem) { + if index < self.transfers.len() { + self.transfers[index] = transfer; + } else { + println!("Index out of bounds."); + } + } + pub fn remove_transfer(&mut self, index: usize) { + if index < self.transfers.len() { + self.transfers.remove(index); + } else { + println!("Index out of bounds."); + } + } +} #[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] #[serde(tag = "action_type")] pub enum ActionItem { AddMembers(AddMembersAction), KusamaTreasury(KusamaTreasuryAction), VotingOpenGov(VotingOpenGovAction), + CommunityTransfer(CommunityTransferAction), } impl ActionItem { pub fn option(&self) -> DropdownItem { match self { - ActionItem::AddMembers(_) => { - DropdownItem { - key: "AddMembers".to_string(), - value: "Add Members".to_string(), - } - } - ActionItem::KusamaTreasury(_) => { - DropdownItem { - key: "KusamaTreasury".to_string(), - value: "Kusama - Request treasury spend".to_string(), - } - } - ActionItem::VotingOpenGov(_) => { - DropdownItem { - key: "VotingOpenGov".to_string(), - value: "Kusama - Vote in OpenGov".to_string(), - } - } + ActionItem::AddMembers(_) => DropdownItem { + key: "AddMembers".to_string(), + value: "Add Members".to_string(), + }, + ActionItem::KusamaTreasury(_) => DropdownItem { + key: "KusamaTreasury".to_string(), + value: "Kusama - Request treasury spend".to_string(), + }, + ActionItem::VotingOpenGov(_) => DropdownItem { + key: "VotingOpenGov".to_string(), + value: "Kusama - Vote in OpenGov".to_string(), + }, + ActionItem::CommunityTransfer(_) => DropdownItem { + key: "CommunityTransfer".to_string(), + value: "Community Transfer".to_string(), + }, } } fn to_option(option: String) -> ActionItem { match &*option { "AddMembers" => ActionItem::AddMembers(AddMembersAction::default()), - "KusamaTreasury" => { - ActionItem::KusamaTreasury(KusamaTreasuryAction::default()) - } + "KusamaTreasury" => ActionItem::KusamaTreasury(KusamaTreasuryAction::default()), "VotingOpenGov" => ActionItem::VotingOpenGov(VotingOpenGovAction::default()), + "CommunityTransfer" => ActionItem::CommunityTransfer(CommunityTransferAction::default()), _ => todo!(), } } @@ -199,6 +226,7 @@ impl ActionItem { ActionItem::AddMembers(AddMembersAction::default()).option(), ActionItem::KusamaTreasury(KusamaTreasuryAction::default()).option(), ActionItem::VotingOpenGov(VotingOpenGovAction::default()).option(), + ActionItem::CommunityTransfer(CommunityTransferAction::default()).option(), ] } } @@ -262,7 +290,7 @@ pub fn use_initiative() -> UseInitiativeState { let actions = consume_context::>(); let settings = consume_context::>(); let confirmation = consume_context::>(); - let mut is_loading = use_signal(|| false); + let is_loading = use_signal(|| false); use_hook(|| UseInitiativeState { inner: UseInitiativeInner { info, diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 9a65c3f..ecc46d5 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -188,7 +188,11 @@ }, "actions": { "label": "Actions", + "add_members": { + "title": "Add Members" + }, "kusama_treasury": { + "title": "Kusama Treasury Request", "disclaimer": { "period_1": "The delivery of resources from the treasury will be made instantly once the proposal is approved.", "period_n": "\nImportant: The resources for the other periods will be delivered on the dates established for each one." @@ -196,7 +200,18 @@ "error": "Must be greater than 0", "placeholder": "Delivery date" }, + "community_transfer": { + "title": "Community Transfer", + "dest": { + "placeholder": "Recipient" + }, + "amount": { + "error": "Must be greater than 0", + "placeholder": "Amount" + } + }, "voting_open_gov": { + "title": "Voting Open Gov", "poll_index": "Poll Index", "standard": { "title": "Standard", diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json index c353cd4..61eba6c 100644 --- a/src/locales/es-ES.json +++ b/src/locales/es-ES.json @@ -183,7 +183,11 @@ }, "actions": { "label": "Acciones", + "add_members": { + "title": "Agregar Miembros" + }, "kusama_treasury": { + "title": "Solicitud Tesoro de Kusama", "disclaimer": { "period_1": "La entrega de recursos por parte del tesoro se realizará instantáneamente, una vez la propuesta sea aprobada.", "period_n": "\nImportante: Los recursos de los demás periodos serán entregados en las fechas establecidas para cada uno." @@ -191,7 +195,18 @@ "error": "Debe ser mayor a 0", "placeholder": "Fecha de entrega" }, + "community_transfer": { + "title": "Transferencia Comunitaria", + "dest": { + "placeholder": "Destinatario" + }, + "amount": { + "error": "Debe ser mayor a 0", + "placeholder": "Cantidad" + } + }, "voting_open_gov": { + "title": "Voto en Open Gov", "poll_index": "Número de la propuesta", "standard": { "title": "Standard", diff --git a/src/pages/initiative.rs b/src/pages/initiative.rs index 8d9bcc2..244acfa 100644 --- a/src/pages/initiative.rs +++ b/src/pages/initiative.rs @@ -1,26 +1,23 @@ use std::str::FromStr; -use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; -use dioxus::prelude::*; -use dioxus_std::{i18n::use_i18, translate}; -use futures_util::TryFutureExt; -use wasm_bindgen::prelude::*; use crate::{ components::{ atoms::{ - button::Variant, dropdown::ElementSize, - icon_button::Variant as IconButtonVariant, Arrow, Button, Icon, IconButton, - Step, StepCard, + button::Variant, dropdown::ElementSize, icon_button::Variant as IconButtonVariant, + Arrow, Button, Icon, IconButton, Step, StepCard, }, molecules::{InitiativeActions, InitiativeInfo}, }, hooks::{ use_initiative::{ use_initiative, ActionItem, InitiativeData, InitiativeInfoContent, - InitiativeInitContent, KusamaTreasury, KusamaTreasuryPeriod, VotingOpenGov, + InitiativeInitContent, KusamaTreasury, KusamaTreasuryPeriod, TransferItem, + VotingOpenGov, }, - use_notification::use_notification, use_our_navigator::use_our_navigator, - use_session::use_session, use_spaces_client::use_spaces_client, + use_notification::use_notification, + use_our_navigator::use_our_navigator, + use_session::use_session, + use_spaces_client::use_spaces_client, use_tooltip::{use_tooltip, TooltipItem}, }, pages::onboarding::convert_to_jsvalue, @@ -29,6 +26,11 @@ use crate::{ kusama::system::number, }, }; +use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; +use dioxus::prelude::*; +use dioxus_std::{i18n::use_i18, translate}; +use futures_util::TryFutureExt; +use wasm_bindgen::prelude::*; #[derive(Clone, Debug)] pub enum InitiativeStep { Info, @@ -39,8 +41,8 @@ pub enum InitiativeStep { } #[wasm_bindgen] extern "C" { - #[wasm_bindgen(catch, js_namespace = window, js_name = topupThenInitiativeSetup)] - pub async fn topup_then_initiative_setup( + #[wasm_bindgen(catch, js_namespace = window, js_name = initiativeSetup)] + pub async fn initiative_setup( community_id: u16, initiative_id: u16, room_id: JsValue, @@ -49,6 +51,7 @@ extern "C" { membership_accounts_remove: JsValue, periods_treasury_request: JsValue, proposals_voting_open_gov: JsValue, + community_transfers: JsValue, ) -> Result; } const BLOCK_TIME_IN_SECONDS: i64 = 6; @@ -305,6 +308,36 @@ pub fn Initiative(id: u16) -> Element { .map(|v| v.serialize_vote_type()) .collect::>(); log::info!("votiong_open_gov_action {:?}", votiong_open_gov_action); + let community_transfer_action = initiative + .get_actions() + .into_iter() + .filter_map(|action| { + match action { + ActionItem::CommunityTransfer(community_transfer_action) => { + Some( + community_transfer_action + .transfers + .clone() + .into_iter() + .filter_map(|transfer| { + if transfer.value > 0 { + Some(transfer) + } else { + None + } + }) + .collect::>(), + ) + } + _ => None, + } + }) + .collect::>>(); + let community_transfer_action = community_transfer_action + .into_iter() + .flat_map(|v| v.into_iter()) + .collect::>(); + log::info!("community_transfer_action {:?}", community_transfer_action); let votiong_open_gov_action = convert_to_jsvalue( &votiong_open_gov_action, ) @@ -312,6 +345,12 @@ pub fn Initiative(id: u16) -> Element { log::warn!("Malformed voting open gov"); translate!(i18, "errors.form.initiative_creation") })?; + let community_transfer_action = convert_to_jsvalue( + &community_transfer_action, + ).map_err(|_| { + log::warn!("Malformed voting open gov"); + translate!(i18, "errors.form.initiative_creation") + })?; let treasury_action = convert_to_jsvalue(&treasury_action) .map_err(|_| { log::warn!("Malformed membership accounts add"); @@ -339,7 +378,7 @@ pub fn Initiative(id: u16) -> Element { log::warn!("Malformed remark"); translate!(i18, "errors.form.initiative_creation") })?; - topup_then_initiative_setup( + initiative_setup( id, last_initiative, room_id, @@ -348,6 +387,7 @@ pub fn Initiative(id: u16) -> Element { membership_accounts_remove, treasury_action, votiong_open_gov_action, + community_transfer_action ) .await .map_err(|e| { @@ -401,11 +441,8 @@ fn convert_treasury_to_period( current_date_millis: u64, ) -> KusamaTreasuryPeriod { if treasury.date != "" { - let future_block = calculate_future_block( - current_block, - current_date_millis, - &treasury.date, - ); + let future_block = + calculate_future_block(current_block, current_date_millis, &treasury.date); KusamaTreasuryPeriod { blocks: Some(future_block as u64), amount: treasury.amount, diff --git a/src/pages/vote.rs b/src/pages/vote.rs index a99076a..ffa1754 100644 --- a/src/pages/vote.rs +++ b/src/pages/vote.rs @@ -278,6 +278,7 @@ pub fn Vote(id: u16, initiativeid: u16) -> Element { ActionItem::AddMembers(action) => action.members.len(), ActionItem::KusamaTreasury(action) => action.periods.len(), ActionItem::VotingOpenGov(action) => action.proposals.len(), + ActionItem::CommunityTransfer(action) => action.transfers.len() } }) .sum::()