use dioxus::prelude::*;
use matrix_sdk::ruma::api::client::room::create_room::v3::Request as CreateRoomRequest;
use matrix_sdk::ruma::api::client::room::Visibility;
use matrix_sdk::ruma::OwnedRoomId;
use crate::components::modal::Modal;
use crate::state::app_state::AppState;
#[component]
pub fn CreateSpaceDialog(on_close: EventHandler<()>) -> Element {
let state = use_context::<Signal<AppState>>();
let mut space_name = use_signal(|| String::new());
let mut space_topic = use_signal(|| String::new());
let mut is_public = use_signal(|| false);
let mut is_creating = use_signal(|| false);
let mut create_error = use_signal(|| Option::<String>::None);
let mut create_success = use_signal(|| false);
let on_create = move |_| {
let name = space_name.read().clone();
if name.trim().is_empty() {
create_error.set(Some("Space name is required".to_string()));
return;
}
is_creating.set(true);
create_error.set(None);
let topic = space_topic.read().clone();
let public = *is_public.read();
spawn(async move {
match create_space(state, &name, &topic, public).await {
Ok(room_id) => {
tracing::info!("Space created: {room_id}");
create_success.set(true);
}
Err(e) => {
tracing::error!("Failed to create space: {e}");
create_error.set(Some(e));
}
}
is_creating.set(false);
});
};
let is_busy = *is_creating.read();
let succeeded = *create_success.read();
let err_msg = create_error.read().clone();
rsx! {
Modal {
title: "Create Space".to_string(),
on_close: move |_| on_close.call(()),
div {
class: "create-space-dialog",
if succeeded {
div {
style: "padding: 16px; text-align: center;",
p {
style: "color: var(--success-text, #4caf50); font-size: 16px; margin-bottom: 12px;",
"Space created successfully!"
}
button {
style: "padding: 8px 20px; border: none; border-radius: 6px; background: var(--accent-color, #4a9eff); color: white; cursor: pointer;",
onclick: move |_| on_close.call(()),
"Close"
}
}
}
if !succeeded {
div {
class: "create-space-dialog__form",
if let Some(ref err) = err_msg {
div {
style: "padding: 8px 12px; background: var(--error-bg, #3a1a1a); color: var(--error-text, #f44336); border-radius: 6px; margin-bottom: 12px;",
"{err}"
}
}
div {
style: "margin-bottom: 16px;",
label {
style: "display: block; font-size: 13px; font-weight: 600; margin-bottom: 4px; color: var(--text-secondary, #888);",
"Space Name"
}
input {
style: "width: 100%; padding: 8px 12px; border: 1px solid var(--border-color, #333); border-radius: 6px; background: var(--bg-primary, #0d0d1a); color: var(--text-primary, #e0e0e0); font-size: 14px;",
r#type: "text",
placeholder: "e.g. My Team",
value: "{space_name}",
oninput: move |evt| space_name.set(evt.value()),
disabled: is_busy,
autofocus: true,
}
}
div {
style: "margin-bottom: 16px;",
label {
style: "display: block; font-size: 13px; font-weight: 600; margin-bottom: 4px; color: var(--text-secondary, #888);",
"Topic (optional)"
}
input {
style: "width: 100%; padding: 8px 12px; border: 1px solid var(--border-color, #333); border-radius: 6px; background: var(--bg-primary, #0d0d1a); color: var(--text-primary, #e0e0e0); font-size: 14px;",
r#type: "text",
placeholder: "What is this space about?",
value: "{space_topic}",
oninput: move |evt| space_topic.set(evt.value()),
disabled: is_busy,
}
}
div {
style: "margin-bottom: 16px;",
label {
style: "display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px;",
input {
r#type: "checkbox",
checked: *is_public.read(),
oninput: move |evt| is_public.set(evt.value() == "true"),
}
span { "Public space (anyone can find and join)" }
}
}
div {
style: "display: flex; justify-content: flex-end; gap: 8px;",
button {
style: "padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border-color, #333); background: transparent; color: var(--text-primary, #e0e0e0); cursor: pointer;",
onclick: move |_| on_close.call(()),
disabled: is_busy,
"Cancel"
}
button {
style: "padding: 8px 16px; border-radius: 6px; border: none; background: var(--accent-color, #4a9eff); color: white; cursor: pointer;",
onclick: on_create,
disabled: space_name.read().trim().is_empty() || is_busy,
if is_busy { "Creating..." } else { "Create Space" }
}
}
}
}
}
}
}
}
async fn create_space(
state: Signal<AppState>,
name: &str,
topic: &str,
is_public: bool,
) -> Result<OwnedRoomId, String> {
let client = { state.read().client.clone() };
let client = client.ok_or_else(|| "Not logged in".to_string())?;
let mut request = CreateRoomRequest::new();
request.name = Some(name.to_string());
if !topic.is_empty() {
request.topic = Some(topic.to_string());
}
request.visibility = if is_public {
Visibility::Public
} else {
Visibility::Private
};
let creation_content = serde_json::json!({
"type": "m.space"
});
request.creation_content = Some(matrix_sdk::ruma::serde::Raw::from_json(
serde_json::value::to_raw_value(&creation_content)
.map_err(|e| format!("Failed to serialize creation content: {e}"))?
));
let response = client
.create_room(request)
.await
.map_err(|e| format!("Failed to create space: {e}"))?;
Ok(response.room_id().to_owned())
}