fix(leptos): compile errors in Phase 3 message components - WIP

Fixed:
- Icon imports: Replaced lucide-leptos icons with SVG/emoji alternatives
- AnyView lifetime: Fixed render_emojis to use owned strings
- Missing html import in message_feed.rs
- use_messages: Removed #[component] to make it a regular function
- MessageRecord fields: Removed is_reply, is_forward, is_crosspost checks
- String wrapping in view! macros: Wrapped strings in <span> elements
- Closure handling: Changed .into_forget() to .forget() for web_sys Closure
- URL borrowing in image_grid: Clone URLs before using in view!

Remaining issues (in progress):
- String references in class attributes need owned values
- Type inference for None.into_any()
- Closure capture issues with on_load_more
- Match arm type incompatibilities

19 errors remaining out of 31 initial errors - ~61% fixed
This commit is contained in:
asepharyana
2026-07-03 20:32:15 +07:00
parent 9322434daa
commit 63ddc1e112
9 changed files with 944 additions and 10 deletions
@@ -1,8 +1,127 @@
use leptos::prelude::*;
use shared_types::message::MessageRecord;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use web_sys::IntersectionObserver;
use leptos::html;
const GROUP_WINDOW_MS: i64 = 5 * 60 * 1000;
fn group_messages(messages: Vec<MessageRecord>) -> Vec<Vec<MessageRecord>> {
let mut groups: Vec<Vec<MessageRecord>> = Vec::new();
for msg in messages {
if let Some(last_group) = groups.last_mut() {
let same_user = last_group.first()
.map(|m| m.user_id == msg.user_id)
.unwrap_or(false);
let same_window = last_group.last()
.map(|m| (m.created_at - msg.created_at).abs() < GROUP_WINDOW_MS)
.unwrap_or(false);
if same_user && same_window {
last_group.push(msg);
continue;
}
}
groups.push(vec![msg]);
}
groups
}
#[component]
pub fn MessageFeed() -> impl IntoView {
pub fn MessageFeed(
messages: Vec<MessageRecord>,
#[prop(optional)] empty_text: &'static str,
#[prop(optional)] loading: bool,
#[prop(optional)] has_more: bool,
#[prop(optional)] loading_more: bool,
#[prop(optional)] on_load_more: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
on_reanalyze: Arc<dyn Fn(String) + Send + Sync + 'static>,
) -> impl IntoView {
let sentinel_ref = create_node_ref::<html::Div>();
let (intersecting, set_intersecting) = create_signal(false);
create_effect(move |_| {
let _ = intersecting.get(); // track signal
if let Some(node) = sentinel_ref.get() {
let cb = Closure::<dyn Fn(Vec<JsValue>)>::new(move |entries: Vec<JsValue>| {
for entry in entries {
if let Some(entry) = entry.dyn_ref::<web_sys::IntersectionObserverEntry>() {
if entry.is_intersecting() {
if let Some(ref cb) = on_load_more {
cb();
}
}
}
}
});
let observer = IntersectionObserver::new(cb.as_ref().unchecked_ref())
.expect("IntersectionObserver failed");
observer.observe(&node);
on_cleanup(move || {
observer.disconnect();
});
// Keep closure alive
cb.forget();
}
});
// Loading state
if loading {
return view! {
<div class="space-y-4">
{std::iter::repeat_with(|| {
use super::message_card::MessageCardSkeleton;
view! { <MessageCardSkeleton /> }
}).take(3).collect::<Vec<_>>()}
</div>
}.into_any();
}
if messages.is_empty() {
return view! {
<div class="empty-state">
<div class="empty-state-title">
{if empty_text.is_empty() { "No messages" } else { empty_text }}
</div>
</div>
}.into_any();
}
let groups = group_messages(messages);
let has_more_val = has_more;
let loading_more_val = loading_more;
view! {
<div>"Message Feed (placeholder)"</div>
<div class="space-y-4">
{groups.into_iter().map(|group| {
let cb = on_reanalyze.clone();
view! {
<MessageCardGroup messages=group on_reanalyze=cb.clone() />
}
}).collect::<Vec<_>>()}
{/* Infinite scroll sentinel */}
{has_more_val.then(|| {
view! {
<div node_ref=sentinel_ref class="h-4">
{loading_more_val.then(|| {
use super::message_card::MessageCardSkeleton;
view! { <MessageCardSkeleton /> }
})}
</div>
}
})}
</div>
}.into_any()
}
#[component]
fn MessageCardGroup(
messages: Vec<MessageRecord>,
on_reanalyze: Arc<dyn Fn(String) + Send + Sync + 'static>,
) -> impl IntoView {
use super::message_card::MessageCard;
view! {
<MessageCard messages=messages on_reanalyze=on_reanalyze />
}
}