synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;
use matrix_sdk::ruma::OwnedRoomId;

use crate::components::modal::Modal;
use crate::state::app_state::AppState;

/// Poll creation dialog.
#[component]
pub fn PollCreator(
    room_id: String,
    on_close: EventHandler<()>,
) -> Element {
    let state = use_context::<Signal<AppState>>();
    let mut question = use_signal(|| String::new());
    let mut answers = use_signal(|| vec!["".to_string(), "".to_string()]);
    let mut is_sending = use_signal(|| false);
    let mut error_msg = use_signal(|| Option::<String>::None);

    let can_send = !question.read().trim().is_empty()
        && answers.read().iter().filter(|a| !a.trim().is_empty()).count() >= 2;

    rsx! {
        Modal {
            title: "Create Poll".to_string(),
            on_close: move |_| on_close.call(()),
            div {
                class: "poll-creator",

                div {
                    class: "poll-creator__field",
                    label { "Question" }
                    input {
                        class: "poll-creator__question-input",
                        r#type: "text",
                        placeholder: "Ask a question...",
                        value: "{question}",
                        oninput: move |evt| question.set(evt.value()),
                    }
                }

                div {
                    class: "poll-creator__field",
                    label { "Answers" }
                    for (idx, _answer) in answers.read().iter().enumerate() {
                        {
                            let val = answers.read()[idx].clone();
                            rsx! {
                                div {
                                    class: "poll-creator__answer-row",
                                    key: "answer-{idx}",
                                    input {
                                        class: "poll-creator__answer-input",
                                        r#type: "text",
                                        placeholder: "Option {idx + 1}",
                                        value: "{val}",
                                        oninput: move |evt| {
                                            answers.write()[idx] = evt.value();
                                        },
                                    }
                                    if answers.read().len() > 2 {
                                        button {
                                            class: "poll-creator__remove-btn",
                                            onclick: move |_| {
                                                answers.write().remove(idx);
                                            },
                                            ""
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if answers.read().len() < 10 {
                        button {
                            class: "btn btn--secondary btn--sm",
                            onclick: move |_| {
                                answers.write().push(String::new());
                            },
                            "+ Add option"
                        }
                    }
                }

                if let Some(ref err) = *error_msg.read() {
                    div { class: "poll-creator__error", "{err}" }
                }

                div {
                    class: "poll-creator__actions",
                    button {
                        class: "btn btn--secondary",
                        onclick: move |_| on_close.call(()),
                        "Cancel"
                    }
                    button {
                        class: "btn btn--primary",
                        disabled: !can_send || *is_sending.read(),
                        onclick: move |_| {
                            let q = question.read().clone();
                            let ans: Vec<String> = answers.read().iter()
                                .filter(|a| !a.trim().is_empty())
                                .cloned()
                                .collect();
                            let rid = room_id.clone();
                            is_sending.set(true);
                            spawn(async move {
                                let client = { state.read().client.clone() };
                                if let Some(client) = client {
                                    if let Ok(room_id) = OwnedRoomId::try_from(rid.as_str()) {
                                        if let Some(room) = client.get_room(&room_id) {
                                            // Send poll as m.poll.start event using unstable prefix
                                            let _poll_start = serde_json::json!({
                                                "type": "org.matrix.msc3381.poll.start",
                                                "content": {
                                                    "org.matrix.msc3381.poll.start": {
                                                        "question": { "org.matrix.msc1767.text": q },
                                                        "kind": "org.matrix.msc3381.poll.disclosed",
                                                        "max_selections": 1,
                                                        "answers": ans.iter().enumerate().map(|(i, a)| {
                                                            serde_json::json!({
                                                                "id": format!("answer-{i}"),
                                                                "org.matrix.msc1767.text": a,
                                                            })
                                                        }).collect::<Vec<_>>(),
                                                    },
                                                    "org.matrix.msc1767.text": format!("Poll: {q}\n{}", ans.join("\n")),
                                                }
                                            });
                                            // Use custom event type via Raw
                                            // Fallback: send as text message with poll formatting
                                            let poll_text = format!("📊 **Poll: {}**\n{}", q,
                                                ans.iter().enumerate()
                                                    .map(|(i, a)| format!("{}. {}", i + 1, a))
                                                    .collect::<Vec<_>>()
                                                    .join("\n")
                                            );
                                            use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
                                            let content = RoomMessageEventContent::text_plain(&poll_text);
                                            match room.send(content).await {
                                                Ok(_) => {
                                                    tracing::info!("Poll sent");
                                                    on_close.call(());
                                                }
                                                Err(e) => {
                                                    error_msg.set(Some(format!("Failed to send poll: {e}")));
                                                }
                                            }
                                        }
                                    }
                                }
                                is_sending.set(false);
                            });
                        },
                        if *is_sending.read() { "Sending..." } else { "Create Poll" }
                    }
                }
            }
        }
    }
}