feat(rating): add rating system 1-5
This commit is contained in:
parent
66aae845ea
commit
e90866fbd9
23
src/app.rs
23
src/app.rs
|
@ -1,7 +1,7 @@
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
use crate::components::{item_form::ItemForm, items_list::ItemsList};
|
use crate::components::{item_form::ItemForm, items_list::ItemsList};
|
||||||
use crate::models::item::Item;
|
use crate::models::item::{Item, ReviewWithRating};
|
||||||
use crate::nostr::NostrClient;
|
use crate::nostr::NostrClient;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -29,27 +29,26 @@ pub fn App() -> impl IntoView {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new item and review using the unified form
|
// Add a new item and review using the unified form
|
||||||
let add_item = move |name: String, description: String, tags: Vec<(String, String)>, review: String| {
|
let add_item = move |name: String, description: String, tags: Vec<(String, String)>, review: String, rating: u8| {
|
||||||
let new_id = Uuid::new_v4().to_string(); // Generate a unique ID
|
let new_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
set_items.update(|items| {
|
set_items.update(|items| {
|
||||||
let item = Item {
|
let item = Item {
|
||||||
id: new_id.clone(),
|
id: new_id.clone(),
|
||||||
name: name.clone(),
|
name,
|
||||||
description: description.clone(),
|
description,
|
||||||
tags: tags.clone(),
|
tags,
|
||||||
reviews: vec![review.clone()], // Initialize reviews
|
reviews: vec![ReviewWithRating { content: review.clone(), rating }],
|
||||||
};
|
};
|
||||||
items.push(item);
|
items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish item to Nostr
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let nostr_client = NostrClient::new("wss://relay.example.com").await.unwrap();
|
let nostr_client = NostrClient::new("wss://relay.example.com").await.unwrap();
|
||||||
nostr_client.publish_item(name, description, tags).await.unwrap();
|
nostr_client.publish_item("New item added!".to_string(), "".to_string(), vec![]).await.unwrap();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Stylesheet href="/assets/style.css" />
|
<Stylesheet href="/assets/style.css" />
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -2,17 +2,15 @@ use leptos::*;
|
||||||
use leptos_dom::ev::SubmitEvent;
|
use leptos_dom::ev::SubmitEvent;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ItemForm(
|
pub fn ItemForm(on_submit: Box<dyn Fn(String, String, Vec<(String, String)>, String, u8)>) -> impl IntoView {
|
||||||
on_submit: Box<dyn Fn(String, String, Vec<(String, String)>, String) + 'static>
|
|
||||||
) -> impl IntoView {
|
|
||||||
let (name, set_name) = create_signal(String::new());
|
let (name, set_name) = create_signal(String::new());
|
||||||
let (description, set_description) = create_signal(String::new());
|
let (description, set_description) = create_signal(String::new());
|
||||||
let (tags, set_tags) = create_signal(Vec::<(String, String)>::new());
|
let (tags, set_tags) = create_signal(Vec::<(String, String)>::new());
|
||||||
let (tag_key, set_tag_key) = create_signal(String::new());
|
let (tag_key, set_tag_key) = create_signal(String::new());
|
||||||
let (tag_value, set_tag_value) = create_signal(String::new());
|
let (tag_value, set_tag_value) = create_signal(String::new());
|
||||||
let (review, set_review) = create_signal(String::new());
|
let (review, set_review) = create_signal(String::new());
|
||||||
|
let (rating, set_rating) = create_signal(5u8); // Default rating to 5
|
||||||
|
|
||||||
// Handle adding a new tag
|
|
||||||
let add_tag = move |_| {
|
let add_tag = move |_| {
|
||||||
if !tag_key.get().is_empty() && !tag_value.get().is_empty() {
|
if !tag_key.get().is_empty() && !tag_value.get().is_empty() {
|
||||||
set_tags.update(|t| t.push((tag_key.get(), tag_value.get())));
|
set_tags.update(|t| t.push((tag_key.get(), tag_value.get())));
|
||||||
|
@ -21,75 +19,48 @@ pub fn ItemForm(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submission.
|
|
||||||
let handle_submit = move |ev: SubmitEvent| {
|
let handle_submit = move |ev: SubmitEvent| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
on_submit(
|
on_submit(
|
||||||
name.get(), // Item name
|
name.get(),
|
||||||
description.get(), // Item description
|
description.get(),
|
||||||
tags.get().clone(), // Tags
|
tags.get().clone(),
|
||||||
review.get(), // Review
|
review.get(),
|
||||||
|
rating.get(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset values after submission
|
// Reset values
|
||||||
set_name.set(String::new());
|
set_name.set(String::new());
|
||||||
set_description.set(String::new());
|
set_description.set(String::new());
|
||||||
set_tags.set(vec![]);
|
set_tags.set(vec![]);
|
||||||
set_review.set(String::new());
|
set_review.set(String::new());
|
||||||
|
set_rating.set(5);
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<form on:submit=handle_submit>
|
<form on:submit=handle_submit>
|
||||||
// Item Name Input
|
<input type="text" placeholder="Name" on:input=move |e| set_name.set(event_target_value(&e)) />
|
||||||
<input
|
<textarea placeholder="Description" on:input=move |e| set_description.set(event_target_value(&e)) />
|
||||||
type="text"
|
<h3>{ "Add Tags" }</h3>
|
||||||
placeholder="Name"
|
<input type="text" placeholder="Key" on:input=move |e| set_tag_key.set(event_target_value(&e)) />
|
||||||
value={name.get()}
|
<input type="text" placeholder="Value" on:input=move |e| set_tag_value.set(event_target_value(&e)) />
|
||||||
on:input=move |e| set_name.set(event_target_value(&e))
|
<button type="button" on:click=add_tag>{ "Add Tag" }</button>
|
||||||
/>
|
|
||||||
|
|
||||||
// Item Description Input
|
|
||||||
<textarea
|
|
||||||
placeholder="Description"
|
|
||||||
value={description.get()}
|
|
||||||
on:input=move |e| set_description.set(event_target_value(&e))
|
|
||||||
/>
|
|
||||||
|
|
||||||
// Tags Section
|
|
||||||
<div>
|
|
||||||
<h3>{ "Add Tags" }</h3>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Key"
|
|
||||||
value={tag_key.get()}
|
|
||||||
on:input=move |e| set_tag_key.set(event_target_value(&e))
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Value"
|
|
||||||
value={tag_value.get()}
|
|
||||||
on:input=move |e| set_tag_value.set(event_target_value(&e))
|
|
||||||
/>
|
|
||||||
<button type="button" on:click=add_tag>{ "Add Tag" }</button>
|
|
||||||
</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
{tags.get().iter().map(|(key, value)| view! {
|
{tags.get().iter().map(|(key, value)| view! {
|
||||||
<li>{ format!("{}: {}", key, value) }</li>
|
<li>{ format!("{}: {}", key, value) }</li>
|
||||||
}).collect::<Vec<_>>() }
|
}).collect::<Vec<_>>() }
|
||||||
</ul>
|
</ul>
|
||||||
|
<h3>{ "Write a Review" }</h3>
|
||||||
// Review Input
|
<textarea placeholder="Review" on:input=move |e| set_review.set(event_target_value(&e)) />
|
||||||
<div>
|
<h3>{ "Rating (1-5)" }</h3>
|
||||||
<h3>{ "Review" }</h3>
|
<input
|
||||||
<textarea
|
type="number"
|
||||||
placeholder="Write your review here"
|
min="1"
|
||||||
value={review.get()}
|
max="5"
|
||||||
on:input=move |e| set_review.set(event_target_value(&e))
|
value={rating.get()}
|
||||||
/>
|
on:input=move |e| set_rating.set(event_target_value(&e).parse::<u8>().unwrap_or(5))
|
||||||
</div>
|
/>
|
||||||
|
<button type="submit">{ "Add Item" }</button>
|
||||||
// Submit Button
|
|
||||||
<button type="submit">{ "Add Item with Review" }</button>
|
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,19 @@ pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
<div>
|
<div>
|
||||||
<h2>{ "Items" }</h2>
|
<h2>{ "Items" }</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{move || items.get().iter().enumerate().map(|(i, item)| view! {
|
{move || items.get().iter().enumerate().map(|(i, item)| view! {
|
||||||
<li key={i.to_string()}>
|
<li key={i.to_string()}>
|
||||||
<strong>{ item.name.clone() }</strong> - { item.description.clone() }
|
<strong>{ item.name.clone() }</strong> - { item.description.clone() }
|
||||||
<h4>{ "Tags:" }</h4>
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<h4>{ "Tags:" }</h4>
|
||||||
{item.tags.iter().map(|(key, value)| view! {
|
{item.tags.iter().map(|(key, value)| view! {
|
||||||
<li>{ format!("{}: {}", key, value) }</li>
|
<li>{ key.clone() + ": " + value }</li>
|
||||||
}).collect::<Vec<_>>()}
|
}).collect::<Vec<_>>()}
|
||||||
</ul>
|
</ul>
|
||||||
<h4>{ "Reviews:" }</h4>
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<h4>{ "Reviews:" }</h4>
|
||||||
{item.reviews.iter().map(|review| view! {
|
{item.reviews.iter().map(|review| view! {
|
||||||
<li>{ review.clone() }</li>
|
<li>{ format!("Rating: {}/5 - {}", review.rating, review.content) }</li>
|
||||||
}).collect::<Vec<_>>()}
|
}).collect::<Vec<_>>()}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -30,3 +30,4 @@ pub fn ItemsList(items: ReadSignal<Vec<Item>>) -> impl IntoView {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,11 @@ pub struct Item {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub tags: Vec<(String, String)>,
|
pub tags: Vec<(String, String)>,
|
||||||
pub reviews: Vec<String>,
|
pub reviews: Vec<ReviewWithRating>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ReviewWithRating {
|
||||||
|
pub content: String,
|
||||||
|
pub rating: u8, // Ratings from 1 to 5
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue