summaryrefslogblamecommitdiffstats
path: root/src/components/roster_list/contact_request_manager.rs
blob: 174e67720c83c5a514bba446028142ba7f88920f (plain) (tree)





































































































































































































                                                                                                                                                                                                                                                               
use std::{collections::HashSet, str::FromStr};

use filamento::{error::{CommandError, SubscribeError}, roster::ContactStoreFields};
use jid::{BareJID, JID};
use leptos::{html::Input, prelude::*};
use reactive_stores::Store;
use thiserror::Error;

use crate::{client::Client, roster::{Roster, RosterStoreFields}};

#[derive(Clone, Debug, Error)]
pub enum AddContactError {
    #[error("Missing JID")]
    MissingJID,
    #[error("Invalid JID: {0}")]
    InvalidJID(#[from] jid::ParseError),
    #[error("Subscription: {0}")]
    Db(#[from] CommandError<SubscribeError>),
}

#[component]
// TODO: rename
pub fn AddContact() -> impl IntoView {
    let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context");
    let set_requests: WriteSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions write signal in context");
    let roster: Store<Roster>  = use_context().expect("no roster in context");

    let jid = RwSignal::new("".to_string());
    // TODO: compartmentalise into error component, form component...
    let (error, set_error) = signal(None::<AddContactError>);
    let error_message = move || {
        error.with(|error| {
            if let Some(error) = error {
                view! { <div class="error">{error.to_string()}</div> }.into_any()
            } else {
                view! {}.into_any()
            }
        })
    };
    let (add_contact_pending, set_add_contact_pending) = signal(false);

    let client = use_context::<Client>().expect("client not in context");
    let client2 = client.clone();
    let client3 = client.clone();
    let client4 = client.clone();

    let add_contact= Action::new_local(move |_| {
        let client = client.clone();
        async move {
            set_add_contact_pending.set(true);

            if jid.read_untracked().is_empty() {
                set_error.set(Some(AddContactError::MissingJID));
                set_add_contact_pending.set(false);
                return;
            }

            let jid = match JID::from_str(&jid.read_untracked()) {
                Ok(j) => j.to_bare(),
                Err(e) => {
                    set_error.set(Some(e.into()));
                    set_add_contact_pending.set(false);
                    return;
                }
            };

            let chat_jid = jid;
            // TODO: more options?
            match client.buddy_request(chat_jid).await {
                Ok(c) => c,
                Err(e) => {
                    set_error.set(Some(e.into()));
                    set_add_contact_pending.set(false);
                   return;
                },
            };

            set_add_contact_pending.set(false);
        }
    });

    let jid_input = NodeRef::<Input>::new();
    let _focus = Effect::new(move |_| {
        if let Some(input) = jid_input.get() {
            let _ = input.focus();
            input.set_text_content(Some(""));
            // input.style("height: 0");
            // let height = input.scroll_height();
            // input.style(format!("height: {}px", height));
        }
    });

    let outgoing = move || roster.contacts().get().into_iter().filter(|(jid, contact)| {
        match *contact.contact.subscription().read() {
            filamento::roster::Subscription::None => false,
            filamento::roster::Subscription::PendingOut => true,
            filamento::roster::Subscription::PendingIn => false,
            filamento::roster::Subscription::PendingInPendingOut => true,
            filamento::roster::Subscription::OnlyOut => false,
            filamento::roster::Subscription::OnlyIn => false,
            filamento::roster::Subscription::OutPendingIn => false,
            filamento::roster::Subscription::InPendingOut => true,
            filamento::roster::Subscription::Buddy => false,
        }
    }).collect::<Vec<_>>();

    let accept_friend_request = Action::new_local(move |jid: &BareJID| {
        let client = client2.clone();
        let jid = jid.clone();
        async move {
            // TODO: error
            client.accept_buddy_request(jid).await;
        }
    });

    let reject_friend_request = Action::new_local(move |jid: &BareJID| {
        let client = client3.clone();
        let jid = jid.clone();
        async move {
            // TODO: error
            client.unsubscribe_contact(jid.clone()).await;
            set_requests.write().remove(&jid);
        }
    });

    let cancel_subscription_request = Action::new_local(move |jid: &BareJID| {
        let client = client4.clone();
        let jid = jid.clone();
        async move {
            // TODO: error
            client.unsubscribe_from_contact(jid).await;

        }
    });

    view! {
        <div class="add-contact-menu">
        <div>
            {error_message}
            <form on:submit=move |ev| {
                ev.prevent_default();
                add_contact.dispatch(());
            }>
                <input
                    disabled=add_contact_pending
                    placeholder="JID"
                    type="text"
                    node_ref=jid_input
                    bind:value=jid
                    name="jid"
                    id="jid"
                    autofocus="true"
                />
                <input disabled=add_contact_pending class="button" type="submit" value="Send Friend Request" />
            </form>
        </div>
        {move || if !requests.read().is_empty() {
            view! {
                <div>
                    <h3>Incoming Subscription Requests</h3>
                    <For each=move || requests.get() key=|request| request.clone() let(request)>
                        {
                            let request2 = request.clone();
                            let request3 = request.clone();
                            let jid_string = move || request.to_string();
                            view! {
                            <div class="jid-with-button"><div class="jid">{jid_string}</div>
                            <div><div class="button" on:click=move |_| { accept_friend_request.dispatch(request2.clone()); } >Accept</div><div class="button" on:click=move |_| { reject_friend_request.dispatch(request3.clone()); } >Reject</div></div></div>
                            }
                        }
                    </For>
                </div>
            }.into_any()
        } else {
            view! {}.into_any()
        }}
        {move || if !outgoing().is_empty() {
            view! {
                <div>
                    <h3>Pending Outgoing Subscription Requests</h3>
                    <For each=move || outgoing() key=|(jid, _contact)| jid.clone() let((jid, contact))>
                        {
                            let jid2 = jid.clone();
                            let jid_string = move || jid.to_string();
                            view! {
                            <div class="jid-with-button"><div class="jid">{jid_string}</div><div class="button" on:click=move |_| { cancel_subscription_request.dispatch(jid2.clone()); } >Cancel</div></div>
                            }
                        }
                    </For> 
                </div>
            }.into_any()
        } else {
            view! {}.into_any()
        }}
        </div>
    }
}