Fix braindump display and switching issues

- Add echo API endpoint for Echo component
- Export Echo component from ui crate to fix compilation errors
- Fix notes not showing after save by adding refresh_notes callback that updates the notes resource
- Fix note content not updating when switching notes by using reactive signal for note_id which triggers note resource reload on prop changes

💘 Generated with Crush
This commit is contained in:
2026-02-04 12:03:21 +01:00
parent 962fd1a154
commit 25e498aff0
3 changed files with 46 additions and 12 deletions

View File

@@ -113,3 +113,8 @@ pub async fn toggle_pin_note(id: String) -> Result<(), ServerFnError> {
Err(ServerFnError::new("Note not found".to_string())) Err(ServerFnError::new("Note not found".to_string()))
} }
} }
#[post("/api/echo")]
pub async fn echo(message: String) -> Result<String, ServerFnError> {
Ok(format!("Echo: {}", message))
}

View File

@@ -1,7 +1,10 @@
//! This crate contains all shared UI for the workspace. //! This is the UI package for the braindump application.
mod hero; mod hero;
pub use hero::Hero; pub use hero::Hero;
mod navbar; mod navbar;
pub use navbar::Navbar; pub use navbar::Navbar;
mod echo;
pub use echo::Echo;

View File

@@ -14,6 +14,17 @@ fn BraindumpLayout() -> Element {
api::list_notes().await.unwrap_or_default() api::list_notes().await.unwrap_or_default()
}); });
let refresh_notes = {
let mut notes_ref = notes.clone();
move || {
spawn(async move {
if let Ok(new_notes) = api::list_notes().await {
notes_ref.set(Some(new_notes));
}
});
}
};
let mut selected_note_id = use_signal(|| None::<String>); let mut selected_note_id = use_signal(|| None::<String>);
let search_query = use_signal(|| String::new()); let search_query = use_signal(|| String::new());
let filter_tag = use_signal(|| None::<String>); let filter_tag = use_signal(|| None::<String>);
@@ -40,6 +51,11 @@ fn BraindumpLayout() -> Element {
on_go_back: move |_| { on_go_back: move |_| {
selected_note_id.set(None); selected_note_id.set(None);
}, },
on_save: refresh_notes,
on_delete: move |_| {
selected_note_id.set(None);
refresh_notes();
},
} }
}, },
None => rsx! { None => rsx! {
@@ -52,6 +68,7 @@ fn BraindumpLayout() -> Element {
vec![] vec![]
).await { ).await {
selected_note_id.set(Some(id)); selected_note_id.set(Some(id));
refresh_notes();
} }
}); });
} }
@@ -158,7 +175,6 @@ fn SidebarContent(
div { class: "tags-section", div { class: "tags-section",
div { class: "tags-label", "Tags" } div { class: "tags-label", "Tags" }
div { class: "tags-list", div { class: "tags-list",
// Tag filtering placeholder
button { class: "tag", "Work" } button { class: "tag", "Work" }
button { class: "tag", "Personal" } button { class: "tag", "Personal" }
button { class: "tag", "Ideas" } button { class: "tag", "Ideas" }
@@ -243,10 +259,16 @@ fn EmptyState(on_new_note: EventHandler<MouseEvent>) -> Element {
} }
#[component] #[component]
fn NoteEditor(note_id: String, on_go_back: EventHandler<MouseEvent>) -> Element { fn NoteEditor(
let note_id_clone_for_resource = note_id.clone(); note_id: String,
on_go_back: Callback<MouseEvent>,
on_save: Callback<()>,
on_delete: Callback<()>,
) -> Element {
let mut note_id_signal = use_signal(|| note_id.clone());
let note = use_resource(move || { let note = use_resource(move || {
let id = note_id_clone_for_resource.clone(); let id = note_id_signal().clone();
async move { async move {
api::get_note(id).await.ok() api::get_note(id).await.ok()
} }
@@ -257,6 +279,10 @@ fn NoteEditor(note_id: String, on_go_back: EventHandler<MouseEvent>) -> Element
let mut tags = use_signal(|| String::new()); let mut tags = use_signal(|| String::new());
let mut is_pinned = use_signal(|| false); let mut is_pinned = use_signal(|| false);
use_effect(move || {
note_id_signal.set(note_id.clone());
});
use_effect(move || { use_effect(move || {
let note_val = note.read_unchecked(); let note_val = note.read_unchecked();
if let Some(Some(n)) = note_val.as_ref() { if let Some(Some(n)) = note_val.as_ref() {
@@ -267,9 +293,8 @@ fn NoteEditor(note_id: String, on_go_back: EventHandler<MouseEvent>) -> Element
} }
}); });
let note_id_for_save = note_id.clone();
let save_note = move |_: MouseEvent| { let save_note = move |_: MouseEvent| {
let id = note_id_for_save.clone(); let id = note_id_signal().clone();
let title_val = title(); let title_val = title();
let content_val = content(); let content_val = content();
let tags_input = tags(); let tags_input = tags();
@@ -282,24 +307,25 @@ fn NoteEditor(note_id: String, on_go_back: EventHandler<MouseEvent>) -> Element
spawn(async move { spawn(async move {
let _ = api::update_note(id, title_val, content_val, tags_list, pinned).await; let _ = api::update_note(id, title_val, content_val, tags_list, pinned).await;
on_save(());
}); });
}; };
let note_id_for_delete = note_id.clone();
let delete_note = move |_: MouseEvent| { let delete_note = move |_: MouseEvent| {
let id = note_id_for_delete.clone(); let id = note_id_signal().clone();
spawn(async move { spawn(async move {
let _ = api::delete_note(id).await; let _ = api::delete_note(id).await;
on_delete(());
}); });
}; };
let note_id_for_toggle = note_id.clone();
let toggle_pin = move |_: MouseEvent| { let toggle_pin = move |_: MouseEvent| {
let id = note_id_for_toggle.clone(); let id = note_id_signal().clone();
let current_pin = is_pinned(); let current_pin = is_pinned();
spawn(async move { spawn(async move {
let _ = api::toggle_pin_note(id).await; let _ = api::toggle_pin_note(id).await;
is_pinned.set(!current_pin); is_pinned.set(!current_pin);
on_save(());
}); });
}; };
@@ -308,7 +334,7 @@ fn NoteEditor(note_id: String, on_go_back: EventHandler<MouseEvent>) -> Element
div { class: "editor-header", div { class: "editor-header",
button { button {
class: "back-button", class: "back-button",
onclick: move |evt| on_go_back.call(evt), onclick: on_go_back,
"← Back" "← Back"
} }
div { class: "editor-actions", div { class: "editor-actions",