feat(rating): add rating system 1-5

This commit is contained in:
Ryan Mwangi 2024-12-17 13:39:41 +03:00
parent 66aae845ea
commit e90866fbd9
4 changed files with 51 additions and 74 deletions

View File

@ -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>

View File

@ -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>
} }
} }

View File

@ -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>
} }
} }

View File

@ -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
}