Skip to content

Commit f38e842

Browse files
committed
add commenting feature
1 parent e2224cc commit f38e842

File tree

15 files changed

+199
-29
lines changed

15 files changed

+199
-29
lines changed

devblog/devblog/Controllers/AccountsController.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ public async Task<IActionResult> SignIn(SignIn signIn)
148148
{
149149
token = new JwtSecurityTokenHandler().WriteToken(token),
150150
expiration = token.ValidTo,
151-
username = user.UserName.Normalize()
151+
username = user.UserName.Normalize(),
152+
authenticated = true,
152153
});
153154
}
154155
else
@@ -161,12 +162,19 @@ public async Task<IActionResult> SignIn(SignIn signIn)
161162
/// Signs out the currently sign in user
162163
/// </summary>
163164
/// <returns>Task<IActionResult></returns>
164-
[Authorize]
165+
//[Authorize]
165166
[HttpPost("signout")]
166167
public async Task<IActionResult> LogOut()
167168
{
168169
await _signInMgr.SignOutAsync();
169-
return Ok();
170+
171+
return Ok(new
172+
{
173+
token = "",
174+
expiration = "",
175+
username = "",
176+
authenticated = false
177+
});
170178
}
171179

172180
/// <summary>
@@ -215,7 +223,8 @@ public async Task<IActionResult> SignUp(User user)
215223
{
216224
token = new JwtSecurityTokenHandler().WriteToken(token),
217225
expiration = token.ValidTo,
218-
username = user.UserName
226+
username = user.UserName,
227+
authenticated = true,
219228
});
220229
}
221230
else

devblog/devblog/client/src/api.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ const URL: &str = "https://localhost:44482/api/";
66

77
#[derive(Clone, PartialEq)]
88
pub enum Api {
9+
AddComment,
10+
DeleteCurrentAccount,
11+
DeleteAccount(String),
912
GetPage(u32),
1013
GetPost(i32),
1114
GetPostsCount,
1215
GetPagesCount,
1316
GetUsers,
1417
GetCurrentUser,
15-
ToggleSubscribe,
1618
SignIn,
1719
SignUp,
18-
DeleteCurrentAccount,
19-
DeleteAccount(String),
20+
SignOut,
21+
ToggleSubscribe,
2022
}
2123

2224
impl Api {
@@ -51,17 +53,19 @@ impl Api {
5153

5254
fn uri(&self) -> String {
5355
match self {
56+
Api::AddComment => format!("{}comments", URL),
57+
Api::DeleteCurrentAccount => format!("{}accounts", URL),
58+
Api::DeleteAccount(username) => format!("{}accounts/adminDelete/{}", URL, username),
5459
Api::GetPage(num) => format!("{}posts/?page={}", URL, num),
5560
Api::GetPost(id) => format!("{}posts/{}", URL, id),
5661
Api::GetPostsCount => format!("{}posts/countPosts", URL),
5762
Api::GetPagesCount => format!("{}posts/countPages", URL),
5863
Api::GetUsers => format!("{}accounts", URL),
5964
Api::GetCurrentUser => format!("{}accounts/user", URL),
60-
Api::ToggleSubscribe => format!("{}accounts/subscribe", URL),
6165
Api::SignIn => format!("{}accounts/signin", URL),
6266
Api::SignUp => format!("{}accounts/signup", URL),
63-
Api::DeleteCurrentAccount => format!("{}accounts", URL),
64-
Api::DeleteAccount(username) => format!("{}accounts/adminDelete/{}", URL, username),
67+
Api::SignOut => format!("{}accounts/signout", URL),
68+
Api::ToggleSubscribe => format!("{}accounts/subscribe", URL),
6569
}
6670
}
6771
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::{helpers, store::Store, Api, CommentModel};
2+
use gloo::console::log;
3+
use gloo_net::http::{Headers, Method};
4+
use std::ops::Deref;
5+
use stylist::Style;
6+
use web_sys::{Event, HtmlInputElement, HtmlTextAreaElement, SubmitEvent};
7+
use yew::prelude::*;
8+
use yewdux::prelude::*;
9+
10+
const STYLE: &str = include_str!("styles/addComment.css");
11+
12+
#[derive(Properties, PartialEq)]
13+
pub struct Props {
14+
pub post_id: u32,
15+
}
16+
17+
#[function_component(AddComment)]
18+
pub fn add_comment(props: &Props) -> Html {
19+
let style = Style::new(STYLE).unwrap();
20+
let comment = use_state(CommentModel::default);
21+
let store = use_store_value::<Store>();
22+
23+
let onchange = {
24+
let comment = comment.clone();
25+
Callback::from(move |e: Event| {
26+
let mut comment_clone = comment.deref().clone();
27+
let input = e.target_dyn_into::<HtmlTextAreaElement>();
28+
if let Some(value) = input {
29+
comment_clone.content = value.value();
30+
comment.set(comment_clone);
31+
}
32+
})
33+
};
34+
35+
let onsubmit = {
36+
let comment = comment.clone();
37+
let post_id = props.post_id.clone();
38+
Callback::from(move |e: SubmitEvent| {
39+
e.prevent_default();
40+
let auth = format!("Bearer {}", store.token);
41+
let hdrs = Headers::new();
42+
hdrs.append("Authorization", &auth);
43+
hdrs.append("content-type", "application/json");
44+
45+
let new_comment = CommentModel::new(
46+
0,
47+
post_id,
48+
comment.deref().content.clone(),
49+
store.username.clone(),
50+
);
51+
comment.set(new_comment.clone());
52+
53+
let body = Some(helpers::to_jsvalue(new_comment.clone()));
54+
log!("Response: ", body.clone().unwrap());
55+
56+
wasm_bindgen_futures::spawn_local(async move {
57+
let res = Api::AddComment.fetch(Some(hdrs), body, Method::POST).await;
58+
});
59+
})
60+
};
61+
62+
html! {
63+
<div class={style}>
64+
<div class="create-comment">
65+
<form {onsubmit}>
66+
<textarea placeholder="your comments here..."
67+
type="text"
68+
required={true}
69+
value={comment.content.clone()}
70+
onchange={onchange}>
71+
</textarea>
72+
<button>{"Add Comment"}</button>
73+
</form>
74+
</div>
75+
</div>
76+
}
77+
}

devblog/devblog/client/src/components/footer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ pub fn footer() -> Html {
1414
<div class={style}>
1515
<div class="footer">
1616
// <DeleteAccount />
17-
// {loggedIn &&
17+
if store.authenticated {
1818
<span>{"Welcome "}{store.username.clone()}</span>
19-
// }
19+
}
2020
</div>
2121
</div>
2222
}

devblog/devblog/client/src/components/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod add_comment;
12
pub mod comment;
23
pub mod footer;
34
pub mod items;

devblog/devblog/client/src/components/navbar.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
use crate::router::Route;
1+
use crate::{pages::sign_out::SignOut, router::Route, store::Store};
22
use stylist::Style;
33
use yew::prelude::*;
44
use yew_router::prelude::*;
5+
use yewdux::use_store_value;
56

67
const STYLE: &str = include_str!("styles/navbar.css");
78

89
#[function_component(Navbar)]
910
pub fn navbar() -> Html {
1011
let style = Style::new(STYLE).unwrap();
12+
let store = use_store_value::<Store>();
1113

1214
html! {
1315
<div class={style}>
@@ -25,8 +27,14 @@ pub fn navbar() -> Html {
2527
<Link<Route> to={Route::About}>{"About"}</Link<Route>>
2628
<Link<Route> to={Route::Insights}>{"Insights"}</Link<Route>>
2729
<Link<Route> to={Route::Account}>{"Account"}</Link<Route>>
30+
31+
// <Link<Route> to={Route::SignOut}>{"SignOut"}</Link<Route>>
32+
33+
if store.authenticated {
34+
<SignOut />
35+
}
36+
2837
<Link<Route> to={Route::SignIn}>{"SignIn"}</Link<Route>>
29-
<Link<Route> to={Route::SignOut}>{"SignOut"}</Link<Route>>
3038
<Link<Route> to={Route::SignUp}>{"SignUp"}</Link<Route>>
3139
</div>
3240
</nav>

devblog/devblog/client/src/components/post.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
components::{comment::Comment, vote::Vote},
2+
components::{add_comment::AddComment, comment::Comment, vote::Vote},
33
PostModel,
44
};
55
use chrono::{Local, TimeZone};
@@ -41,6 +41,7 @@ pub fn post(props: &Props) -> Html {
4141
<div>{&props.post.description}</div>
4242

4343
// COMMENTS
44+
<AddComment post_id={props.post.id}/>
4445
<div>
4546
{for props.post.comments.iter().map(|comment| {
4647
html! {<Comment comment={comment.clone()} />}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.create-comment {
2+
margin-bottom: 20px;
3+
}
4+
5+
.create-comment form {
6+
display: inline-flex;
7+
flex-direction: column;
8+
width: 100%;
9+
}
10+
11+
.create-comment form input {
12+
width: 10em;
13+
margin: .5em 0 .5em 0;
14+
border: 0 0 1px 0;
15+
}
16+
17+
.create-comment button {
18+
margin-top: .5em;
19+
width: fit-content;
20+
padding: 8px;
21+
}

devblog/devblog/client/src/helpers.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::store::Store;
22
use crate::{helpers, Api};
33
use crate::{router::Route, User, UserField};
44
use gloo::console::log;
5+
// use gloo::utils::format::JsValueSerdeExt;
56
use gloo_net::http::{Headers, Method, Response};
67
use serde::de::DeserializeOwned;
78
use serde::Serialize;
@@ -13,7 +14,7 @@ use yew::{Callback, TargetCast, UseStateHandle};
1314
use yew_router::navigator::Navigator;
1415
use yewdux::Dispatch;
1516

16-
pub fn onchange(user: &UseStateHandle<User>, field: UserField) -> Callback<Event> {
17+
pub fn on_change(user: &UseStateHandle<User>, field: UserField) -> Callback<Event> {
1718
let user = user.clone();
1819
Callback::from(move |e: Event| {
1920
let user_clone = user.deref().clone();
@@ -60,23 +61,31 @@ pub fn on_submit(
6061
let api = Rc::clone(&api);
6162
Callback::from(move |e: SubmitEvent| {
6263
e.prevent_default();
64+
65+
// clone parameter fields to prevent ownership issues with callbacks
66+
let api = Rc::clone(&api);
6367
let dispatch_clone = dispatch.clone();
6468
let nav = nav.clone();
6569
let mut user = user.deref().clone();
6670
user.subscribed = true;
71+
72+
// build headers
6773
let hdrs = Headers::new();
6874
hdrs.append("content-type", "application/json");
69-
let api = Rc::clone(&api);
75+
7076
wasm_bindgen_futures::spawn_local(async move {
7177
let body = Some(helpers::to_jsvalue(user));
7278

7379
// navigate home if the submission is successful
7480
if let Some(res) = api.fetch(Some(hdrs), body, Method::POST).await {
7581
if res.status() == 200 {
7682
let obj: Store = serde_json::from_str(&res.text().await.unwrap()).unwrap();
83+
84+
// log!("Response: ", <JsValue as JsValueSerdeExt>::from_serde(&obj).unwrap());
7785
dispatch_clone.reduce_mut(move |store| {
7886
store.token = obj.token;
7987
store.username = obj.username;
88+
store.authenticated = obj.authenticated;
8089
});
8190
nav.push(&Route::Home);
8291
}
@@ -85,7 +94,7 @@ pub fn on_submit(
8594
})
8695
}
8796

88-
fn to_jsvalue<T>(body: T) -> JsValue
97+
pub fn to_jsvalue<T>(body: T) -> JsValue
8998
where
9099
T: Serialize,
91100
{

devblog/devblog/client/src/models.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use chrono::NaiveDateTime;
1+
use chrono::{NaiveDateTime, Utc};
22
use serde::{Deserialize, Serialize};
33

44
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug, Default)]
@@ -25,6 +25,18 @@ pub struct CommentModel {
2525
pub username: String,
2626
}
2727

28+
impl CommentModel {
29+
pub fn new(id: u32, post_id: u32, content: String, username: String) -> Self {
30+
CommentModel {
31+
id,
32+
post_id,
33+
content,
34+
date: Utc::now().naive_utc(),
35+
username,
36+
}
37+
}
38+
}
39+
2840
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug, Default)]
2941
pub struct ImgModel {
3042
#[serde(rename = "postId")]

0 commit comments

Comments
 (0)