Skip to content

Commit 0418368

Browse files
committed
Add comment form and update skills
1 parent 798db50 commit 0418368

7 files changed

Lines changed: 484 additions & 183 deletions

File tree

src/components/comment_form.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use crate::app::ThemeContext;
2+
use crate::data::comment::Comment;
3+
use chrono::Utc;
4+
use gloo::utils::document;
5+
use patternfly_yew::prelude::OnOptionSelectArgs;
6+
use uuid::Uuid;
7+
use wasm_bindgen::{JsCast, UnwrapThrowExt};
8+
use web_sys::HtmlInputElement;
9+
use yew::prelude::*;
10+
11+
#[derive(Properties, PartialEq)]
12+
pub struct CommentFormProps {
13+
pub post_id: String,
14+
pub on_submit: Callback<Comment>,
15+
pub on_cancel: Callback<()>,
16+
}
17+
18+
#[function_component(CommentForm)]
19+
pub fn comment_form(props: &CommentFormProps) -> Html {
20+
let theme_context = use_context::<ThemeContext>().expect("Theme context not found");
21+
let dark_mode = theme_context.dark_mode;
22+
let author = use_state(|| String::new());
23+
let content = use_state(|| String::new());
24+
25+
// Define color palette based on theme
26+
let bg_primary = if dark_mode {
27+
"bg-[#3A4D39]"
28+
} else {
29+
"bg-[#ECE3CE]"
30+
};
31+
let text_primary = if dark_mode {
32+
"text-[#ECE3CE]"
33+
} else {
34+
"text-[#3A4D39]"
35+
};
36+
let input_bg = if dark_mode {
37+
"bg-[#3A4D39]"
38+
} else {
39+
"bg-[#ECE3CE]"
40+
};
41+
let input_border = if dark_mode {
42+
"border-[#ECE3CE]"
43+
} else {
44+
"border-[#3A4D39]"
45+
};
46+
let input_text = if dark_mode {
47+
"text-[#ECE3CE]"
48+
} else {
49+
"text-[#3A4D39]"
50+
};
51+
let button_bg = if dark_mode {
52+
"bg-[#3A4D39]"
53+
} else {
54+
"bg-[#3A4D39]"
55+
};
56+
let button_text = if dark_mode {
57+
"text-[#ECE3CE]"
58+
} else {
59+
"text-[#ECE3CE]"
60+
};
61+
let button_hover = if dark_mode {
62+
"hover:bg-opacity-80"
63+
} else {
64+
"hover:bg-opacity-80"
65+
};
66+
let button_cancel_bg = if dark_mode {
67+
"bg-red-700"
68+
} else {
69+
"bg-red-600"
70+
};
71+
72+
let on_submit = {
73+
let author = author.clone();
74+
let content = content.clone();
75+
let post_id = props.post_id.clone();
76+
let on_submit = props.on_submit.clone();
77+
78+
Callback::from(move |e: SubmitEvent| {
79+
e.prevent_default();
80+
if !author.is_empty() && !content.is_empty() {
81+
let comment = Comment {
82+
id: Uuid::new_v4().to_string(),
83+
post_id: post_id.clone(),
84+
author: (*author).clone(),
85+
content: (*content).clone(),
86+
created_at: Utc::now(),
87+
};
88+
on_submit.emit(comment);
89+
}
90+
})
91+
};
92+
93+
let handle_author_change = {
94+
let author = author.clone();
95+
Callback::from(move |e: Event| {
96+
let target = e.target().unwrap();
97+
let input = target.dyn_into::<HtmlInputElement>().unwrap_throw();
98+
author.set(input.value())
99+
})
100+
};
101+
let handle_content_change = {
102+
let content = content.clone();
103+
Callback::from(move |e: Event| {
104+
let target = e.target().unwrap();
105+
let input = target.dyn_into::<HtmlInputElement>().unwrap_throw();
106+
content.set(input.value());
107+
})
108+
};
109+
110+
let on_cancel = {
111+
let on_cancel = props.on_cancel.clone();
112+
Callback::from(move |_| {
113+
on_cancel.emit(());
114+
})
115+
};
116+
html! {
117+
<form onsubmit={on_submit} class="space-y-4">
118+
<div>
119+
<label for="comment-name" class="block text-sm font-medium mb-1">{"Your Name"}</label>
120+
<input
121+
type="text"
122+
id="comment-name"
123+
value={(*author).clone()}
124+
onchange={handle_author_change}
125+
required=true
126+
class={classes!(
127+
"mt-1", "block", "w-full", "rounded-md", "border",
128+
input_border, input_bg, input_text,
129+
"shadow-sm", "focus:ring-2", "focus:ring-opacity-50", "p-2"
130+
)}
131+
/>
132+
</div>
133+
134+
<div>
135+
<label for="comment-content" class="block text-sm font-medium mb-1">{"Comment"}</label>
136+
<textarea
137+
id="comment-content"
138+
value={(*content).clone()}
139+
onchange={handle_content_change}
140+
required=true
141+
rows="4"
142+
class={classes!(
143+
"mt-1", "block", "w-full", "rounded-md", "border",
144+
input_border, input_bg, input_text,
145+
"shadow-sm", "focus:ring-2", "focus:ring-opacity-50", "p-2", "resize-y"
146+
)}
147+
></textarea>
148+
</div>
149+
150+
<div class="flex justify-end space-x-3">
151+
<button
152+
type="button"
153+
onclick={on_cancel}
154+
class={classes!(
155+
"px-4", "py-2", "border", "border-transparent", "text-sm", "font-medium",
156+
"rounded-md", "shadow-sm", button_cancel_bg, "text-white", button_hover,
157+
"focus:outline-none", "focus:ring-2", "transition-colors"
158+
)}
159+
>
160+
{"Cancel"}
161+
</button>
162+
163+
<button
164+
type="submit"
165+
class={classes!(
166+
"px-4", "py-2", "border", "border-transparent", "text-sm", "font-medium",
167+
"rounded-md", "shadow-sm", button_bg, button_text, button_hover,
168+
"focus:outline-none", "focus:ring-2", "transition-colors"
169+
)}
170+
>
171+
{"Post Comment"}
172+
</button>
173+
</div>
174+
</form>
175+
}
176+
}

src/components/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
pub(crate) mod comment_form;
12
mod darkmode;
23
pub mod footer;
34
pub mod header;
45
pub(crate) mod layout;
6+
pub mod modal;
57
pub(crate) mod nav;
68
pub(crate) mod nav_context;
79
pub(crate) mod themecontext;

src/components/modal.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// src/components/modal.rs
2+
use patternfly_yew::prelude::*;
3+
use yew::prelude::*;
4+
5+
#[derive(Properties, PartialEq)]
6+
pub struct ModalProps {
7+
pub title: String,
8+
pub is_open: bool,
9+
pub on_close: Callback<()>,
10+
#[prop_or_default]
11+
pub children: Children,
12+
}
13+
14+
#[function_component(ModalCom)]
15+
pub fn modal(props: &ModalProps) -> Html {
16+
let on_close = props.on_close.clone();
17+
18+
html! {
19+
if props.is_open {
20+
<Modal
21+
title={props.title.clone()}
22+
variant={ModalVariant::Medium}
23+
onclose={Some(on_close)}
24+
show_close={true}
25+
>
26+
{ for props.children.iter() }
27+
</Modal>
28+
}
29+
}
30+
}

src/data/comment.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use chrono::{DateTime, Utc};
2+
use serde::{Deserialize, Serialize};
3+
4+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
5+
pub struct Comment {
6+
pub id: String,
7+
pub post_id: String,
8+
pub content: String,
9+
pub author: String,
10+
pub created_at: DateTime<Utc>,
11+
}
12+
13+
#[derive(Clone, Debug, PartialEq)]
14+
pub struct CommentDb {
15+
comments: Vec<Comment>,
16+
}
17+
18+
impl CommentDb {
19+
pub fn new() -> Self {
20+
Self {
21+
comments: Vec::new(),
22+
}
23+
}
24+
25+
pub fn add_comment(&mut self, comment: Comment) {
26+
self.comments.push(comment);
27+
}
28+
pub fn get_comment_for_post(&self, post_id: &str, comment_id: &str) -> Option<&Comment> {
29+
self.comments
30+
.iter()
31+
.find(|comment| comment.post_id == post_id && comment.id == comment_id)
32+
}
33+
pub fn get_comments(&self, post_id: &str) -> Vec<&Comment> {
34+
self.comments
35+
.iter()
36+
.filter(|comment| comment.post_id == post_id)
37+
.collect()
38+
}
39+
}

src/data/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod blog;
2+
pub(crate) mod comment;

0 commit comments

Comments
 (0)