summaryrefslogblamecommitdiffstats
path: root/src/components/roster_list/contact_request_manager.rs
blob: cfb5f28427b1d5940d6d5dbdfd92826c42be04f2 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                                              



                                          




                                      



                                        













                                             




                                                                                 



















                                                                                 
                                                  

























                                                                  

                           
















                                                            



















                                                                                 

























                                                                              




                                      








































































































                                                                                                         


              
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>
    }
}