use crate::users::{FieldUpdate, UserRecord, UserStore};
use iced::Task;
use super::common::SingleLineEditorState;
use super::editor_widget::EditorAction;
pub(crate) fn user_store() -> Result<&'static UserStore, String> {
crate::users::USER_STORE
.get()
.ok_or_else(|| "User store not initialized".to_string())
}
pub(crate) async fn update_user_field(sender: String, workspace: String) -> Result<(), String> {
let store = user_store()?;
let val = if workspace.is_empty() || crate::users::is_personal_workspace(&workspace) {
FieldUpdate::Clear
} else {
FieldUpdate::Set(&workspace)
};
store
.update_user(&sender, FieldUpdate::Unchanged, val, FieldUpdate::Unchanged)
.await
.map_err(|e| e.to_string())
}
#[derive(Debug, Clone)]
pub enum UsersMessage {
Refreshed(Vec<UserRecord>),
RefreshError(String),
DeleteUser(String),
ConfirmDelete(String),
CancelDelete,
DeleteResult(Result<(), String>, String),
SwitchUser(String),
OpenBindInput(String),
CloseBindInput,
BindInputChanged(EditorAction),
SubmitBind(String),
UnbindChannel(String, String),
BindResult(Result<(), String>, String),
Escape,
Toast(super::ToastMessage),
}
pub struct UsersState {
pub(crate) users: Vec<UserRecord>,
pub(crate) load_state: super::common::AsyncLoadState,
pub(crate) delete_target: Option<String>,
pub(crate) deleting: bool,
pub(crate) bind_target: Option<String>,
pub(crate) bind_input: SingleLineEditorState,
pub(crate) bind_error: Option<String>,
pub(crate) binding: bool,
}
impl UsersState {
#[must_use]
pub fn new() -> Self {
Self {
users: Vec::new(),
load_state: super::common::AsyncLoadState::new(),
delete_target: None,
deleting: false,
bind_target: None,
bind_input: SingleLineEditorState::new(""),
bind_error: None,
binding: false,
}
}
#[allow(clippy::unused_self)]
pub fn refresh(&self) -> Task<UsersMessage> {
Task::perform(
async {
let store = user_store()?;
let users = store.list_users().await.map_err(|e| e.to_string())?;
Ok::<_, String>(users)
},
|res| match res {
Ok(users) => UsersMessage::Refreshed(users),
Err(e) => UsersMessage::RefreshError(e),
},
)
}
#[expect(clippy::too_many_lines)]
pub fn update(&mut self, msg: UsersMessage) -> Task<UsersMessage> {
match msg {
UsersMessage::Refreshed(users) => {
self.users = users;
Task::none()
}
UsersMessage::RefreshError(e) => {
self.load_state.fail(e);
Task::none()
}
UsersMessage::DeleteUser(sender) => {
self.delete_target = Some(sender);
Task::none()
}
UsersMessage::ConfirmDelete(sender) => {
self.delete_target = None;
self.deleting = true;
let s = sender;
let s_clone = s.clone();
Task::perform(
async move {
let store = user_store()?;
store.delete_user(&s).await.map_err(|e| e.to_string())
},
move |res| UsersMessage::DeleteResult(res, s_clone),
)
}
UsersMessage::CancelDelete | UsersMessage::Escape => {
self.delete_target = None;
self.bind_target = None;
self.bind_input.clear();
self.bind_error = None;
Task::none()
}
UsersMessage::DeleteResult(Ok(()), _deleted_user) => {
self.deleting = false;
self.load_state.clear_error();
self.refresh()
}
UsersMessage::DeleteResult(Err(e), _deleted_user) => {
self.deleting = false;
self.load_state.fail(e.clone());
Task::done(UsersMessage::Toast(super::ToastMessage::Error(e)))
}
UsersMessage::Toast(_) => Task::none(),
UsersMessage::SwitchUser(_) => {
Task::none()
}
UsersMessage::OpenBindInput(user_name) => {
self.bind_target = Some(user_name);
self.bind_input.clear();
self.bind_error = None;
self.delete_target = None;
Task::none()
}
UsersMessage::CloseBindInput => {
self.bind_target = None;
self.bind_input.clear();
self.bind_error = None;
Task::none()
}
UsersMessage::BindInputChanged(action) => {
if let Some(task) = super::common::focus_navigation_task(&action) {
return task;
}
self.bind_input.apply_action(action);
self.bind_error = None;
Task::none()
}
UsersMessage::SubmitBind(user_name) => {
self.binding = true;
self.bind_error = None;
let user_clone = user_name.clone();
let input = self.bind_input.text().clone();
Task::perform(
async move {
let store = user_store()?;
let identifier = store
.validate_telegram_bind(&user_clone, &input)
.await
.map_err(|e| e.to_string())?;
store
.bind_channel(&user_clone, "telegram", &identifier)
.await
.map_err(|e| e.to_string())
},
move |res| UsersMessage::BindResult(res, user_name),
)
}
UsersMessage::UnbindChannel(user_name, identifier) => {
self.binding = true;
self.bind_error = None;
let user_clone = user_name.clone();
Task::perform(
async move {
let store = user_store()?;
store
.unbind_channel(&user_clone, "telegram", &identifier)
.await
.map_err(|e| e.to_string())
},
move |res| UsersMessage::BindResult(res, user_name),
)
}
UsersMessage::BindResult(Ok(()), _user_name) => {
self.binding = false;
self.bind_target = None;
self.bind_input.clear();
self.bind_error = None;
self.refresh()
}
UsersMessage::BindResult(Err(e), user_name) => {
self.binding = false;
if self.bind_target.as_deref() == Some(&user_name) {
self.bind_error = Some(format!("Failed to bind: {e}"));
} else {
self.load_state.fail(format!("Failed to unbind: {e}"));
}
Task::done(UsersMessage::Toast(super::ToastMessage::Error(e)))
}
}
}
}