Skip to content

Commit a2d8bad

Browse files
committed
WIP
1 parent feaf9d5 commit a2d8bad

File tree

8 files changed

+327
-1
lines changed

8 files changed

+327
-1
lines changed

Diff for: frontend/src/components/Snowflake.module.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.container {
2+
padding-top: 4.75rem;
3+
}

Diff for: frontend/src/components/Snowflake.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import React from "react";
22
import Navbar from "./header/Navbar";
3+
import {Route, Switch} from "react-router-dom";
4+
import FeedPage from "./feed/FeedPage";
5+
import styles from './Snowflake.module.css';
36

47
export default function Snowflake() {
58
return (
69
<>
710
<Navbar/>
8-
<div className="pt-5">
11+
<div className={styles.container}>
12+
<Switch>
13+
<Route path="/" component={FeedPage}/>
14+
</Switch>
915
</div>
1016
</>
1117
)

Diff for: frontend/src/components/feed/CurrentUserInfo.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {useAuth} from "../../hooks/use-auth";
2+
import {Link} from "react-router-dom";
3+
import React from "react";
4+
5+
export default function CurrentUserInfo() {
6+
const auth = useAuth();
7+
const {name, profile_pic: avatar, username, designation, team_name: teamName} = auth.user!;
8+
9+
return (
10+
<div className="box block">
11+
<div className="header pb-0 p-4 has-text-centered">
12+
<figure className="avatar m-auto image mb-3 is-128x128">
13+
<img alt={name}
14+
title={name} className="is-rounded" src={avatar}/>
15+
</figure>
16+
<h1 className="is-size-4">
17+
<Link to={`/profile/${username}`}>
18+
{name}
19+
</Link>
20+
</h1>
21+
<p>
22+
{designation} @ {teamName}
23+
</p>
24+
</div>
25+
<hr/>
26+
<div className="block summary pt-0 p-4 is-flex is-justify-content-space-around">
27+
<div className="is-flex is-flex-direction-column is-align-items-center">
28+
<span className="given-count has-text-weight-bold is-size-4">
29+
{/*{{appreciations_given}}*/}
30+
</span>
31+
<span className="is-uppercase">Given</span>
32+
</div>
33+
<div className="is-flex is-flex-direction-column is-align-items-center">
34+
<span className="received-count has-text-weight-bold is-size-4">
35+
{/*{{appreciations_received}}*/}
36+
</span>
37+
<span className="is-uppercase">Received</span>
38+
</div>
39+
</div>
40+
</div>);
41+
}

Diff for: frontend/src/components/feed/FeedPage.module.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.rightSidebar {
2+
position: sticky;
3+
top: 4.75rem;
4+
}

Diff for: frontend/src/components/feed/FeedPage.tsx

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import React from "react";
2+
import styles from './FeedPage.module.css';
3+
import CurrentUserInfo from "./CurrentUserInfo";
4+
import MostAppreciatedUsers from "./MostAppreciatedUsers";
5+
6+
function Appreciations() {
7+
8+
return <article id="appreciation-{{ appreciation.id }}" className="appreciation media">
9+
<figure className="media-left is-flex">
10+
<div className="participants-avatar image is-48x48">
11+
<img alt="Profile picture of {{ appreciation.creator.name }}"
12+
title="{{ appreciation.creator.name }}"
13+
className="is-rounded" src="{{ appreciation.creator.profile_pic }}"/>
14+
</div>
15+
<div className="participants-avatar image is-48x48">
16+
{/*{% set mentions = appreciation.get_mentions() %}*/}
17+
{/*{% if mentions %}*/}
18+
<img alt="Profile picture of {{ mentions[0].user.name }}"
19+
title="{{ mentions[0].user.name }}"
20+
className="is-rounded" src="{{ mentions[0].user.profile_pic }}"/>
21+
{/*{% endif %}*/}
22+
</div>
23+
</figure>
24+
<div className="media-content">
25+
<div className="content">
26+
<div>
27+
<a href="/profile/{{appreciation.creator.username}}">
28+
<strong>
29+
{/*{appreciation.creator.name}*/}
30+
</strong>
31+
</a>
32+
<small>
33+
<time dateTime="{{ appreciation.created_at | iso_time }}">
34+
{/*{appreciation.created_at | humanize_time}*/}
35+
</time>
36+
</small>
37+
</div>
38+
<div className="mt-2">
39+
{/*{appreciation.content | add_mentions}*/}
40+
</div>
41+
</div>
42+
<nav className="level is-mobile">
43+
<div className="level-left">
44+
<div className="level-item">
45+
{/*{% if appreciation.is_liked_by(user) %}*/}
46+
<form method="post"
47+
action="{{ url_for('appreciation.dislike') }}">
48+
{/*{{like_form.csrf_token}}*/}
49+
<input type="hidden" name="appreciation" value="{{ appreciation.id }}"/>
50+
<button title="Like" type="submit"
51+
className="post-action-button clear-button has-text-danger level-item is-clickable">
52+
<span className="icon is-medium">
53+
<ion-icon name="heart"/>
54+
</span>
55+
</button>
56+
</form>
57+
{/*{% else %}*/}
58+
<form method="post"
59+
action="{{ url_for('appreciation.like') }}">
60+
{/*{{like_form.csrf_token}}*/}
61+
<input type="hidden" name="appreciation" value="{{ appreciation.id }}"/>
62+
<button title="Like" type="submit"
63+
className="post-action-button clear-button has-text-danger level-item is-clickable">
64+
<span className="icon is-medium">
65+
<ion-icon name="heart-outline"/>
66+
</span>
67+
</button>
68+
</form>
69+
{/*{% endif %}*/}
70+
<button type="button" className="clear-button has-text-danger level-item is-clickable"
71+
data-toggle-modal="#likes-{{ appreciation.id }}">
72+
<span>
73+
{/*{{like_count}} {{choose_plural(like_count, 'like', 'likes')}}*/}
74+
</span>
75+
</button>
76+
</div>
77+
<button title="Comment"
78+
className="post-action-button comment-button clear-button has-text-info level-item is-clickable"
79+
data-toggle="#comments-{{ appreciation.id }}">
80+
<span className="icon is-medium">
81+
<ion-icon name="chatbubble-outline"></ion-icon>
82+
</span>
83+
<span>
84+
{/*{{appreciation.comment_count}}*/}
85+
</span>
86+
</button>
87+
<div className="modal" id="likes-{{ appreciation.id }}">
88+
<div className="modal-background"/>
89+
<div className="modal-content">
90+
<div className="box">
91+
<h2 className="is-size-4">Likes</h2>
92+
<div className="menu mt-4">
93+
<ul className="menu-list">
94+
{/*{% for like in appreciation.likes %}*/}
95+
<li className="is-flex pb-2">
96+
<figure className="media-left is-flex">
97+
<div className="participants-avatar image is-48x48">
98+
<img alt="Profile picture of {{ like.user.name }}"
99+
title="{{ like.user.name }}"
100+
className="is-rounded"
101+
src="{{ like.user.profile_pic }}"/>
102+
</div>
103+
</figure>
104+
<div className="content">
105+
<h3 className="is-size-5 has-font-weight-bold mb-0">
106+
{/*{{like.user.name}}*/}
107+
</h3>
108+
<p className="help">
109+
{/*{like.user.designation}}*/}
110+
</p>
111+
</div>
112+
</li>
113+
{/*{% else %}*/}
114+
<li className="is-flex pb-2">
115+
<div className="content">
116+
<h3 className="is-size-5 mb-0 text-center">
117+
No likes yet
118+
</h3>
119+
</div>
120+
</li>
121+
{/*{% endfor %}*/}
122+
</ul>
123+
</div>
124+
</div>
125+
</div>
126+
<button type="reset" className="modal-close is-large" data-action="close"/>
127+
</div>
128+
</div>
129+
</nav>
130+
<div className="block hide is-clipped" id="comments-{{appreciation.id}}">
131+
<form method="post" action="{{ url_for('appreciation.comment') }}">
132+
{/*{{comment_form.csrf_token}}*/}
133+
<input type="hidden" name="appreciation" value="{{appreciation.id}}"/>
134+
<div className="field is-grouped">
135+
<label className="is-sr-only" htmlFor="comment-content-{{ appreciation.id }}">Write a
136+
comment</label>
137+
<div className="control is-expanded">
138+
<textarea id="comment-content-{{ appreciation.id }}" data-has-mentions name="content" rows={2}
139+
className="textarea"/>
140+
</div>
141+
<div className="control">
142+
<button className="button is-primary" type="submit">Comment</button>
143+
</div>
144+
</div>
145+
</form>
146+
{/*{% for comment in appreciation.get_comments() %}*/}
147+
<div className="comment">
148+
<div>
149+
<strong><a>
150+
{/*{{comment.user.name}}*/}
151+
</a></strong>
152+
<small>
153+
<time dateTime="{{ appreciation.created_at | iso_time }}">
154+
{/*{{comment.created_at | humanize_time}}*/}
155+
</time>
156+
</small>
157+
</div>
158+
<p>
159+
{/*{{comment.content | add_mentions}}*/}
160+
</p>
161+
</div>
162+
{/*{% endfor %}*/}
163+
</div>
164+
</div>
165+
</article>;
166+
}
167+
168+
export default function FeedPage() {
169+
return (
170+
<div className="container">
171+
<div className="columns">
172+
<div className="column is-two-thirds">
173+
<div className="box">
174+
<form className="note-form block" method="post" action="{{ url_for('appreciation.appreciate') }}">
175+
<div className="field">
176+
<label htmlFor="note-field" className="is-sr-only">Write a note a @mention to send a note</label>
177+
<div className="control">
178+
<textarea id="note-field" rows={2} className="textarea" data-has-mentions=""
179+
name="{{form.content.name}}"
180+
placeholder="Write a message that includes @mention"/>
181+
</div>
182+
</div>
183+
<div className="field mt-2 send-container is-clipped">
184+
<div className="control">
185+
<button className="button is-primary is-rounded is-medium">
186+
<span className="icon">
187+
<ion-icon name="paper-plane-outline" size="large"/>
188+
</span>
189+
<span>
190+
Hi five!
191+
</span>
192+
</button>
193+
</div>
194+
</div>
195+
</form>
196+
<hr/>
197+
{/*{% for appreciation in appreciations %}*/}
198+
<Appreciations/>
199+
{/*{% endfor %}*/}
200+
</div>
201+
</div>
202+
<div className="column is-one-third">
203+
<div className={styles.rightSidebar}>
204+
<CurrentUserInfo/>
205+
<MostAppreciatedUsers/>
206+
</div>
207+
</div>
208+
</div>
209+
</div>
210+
);
211+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from "react";
2+
3+
export default function MostAppreciatedUsers() {
4+
return (
5+
<div className="panel is-primary block">
6+
<p className="panel-heading has-text-centered">
7+
Most appreciated
8+
</p>
9+
{/*{% for entry in most_appreciated %}*/}
10+
<a href="/profile/{{entry.user.username}}" className="panel-block p-3 is-active is-align-items-center">
11+
<figure className="image is-24x24 mr-1">
12+
<img alt="{{ entry.user.name }}"
13+
title="{{ entry.user.name }}" className="is-rounded" src="{{entry.user.profile_pic}}"/>
14+
</figure>
15+
<span>
16+
{/*{{entry.user.name}}*/}
17+
</span>
18+
<span className="is-flex-grow-1"/>
19+
<span className="has-text-weight-bold">
20+
{/*{{entry.count}}*/}
21+
</span>
22+
</a>
23+
{/*{% endfor %}*/}
24+
</div>);
25+
}

Diff for: frontend/src/hooks/use-async.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {useCallback, useEffect, useState} from "react";
2+
3+
4+
type AsyncFunction<T> = () => Promise<T>
5+
type State = 'idle' | 'pending' | 'success' | 'error';
6+
7+
export function useAsync<T>(asyncFn: AsyncFunction<T>) {
8+
const [status, setStatus] = useState<State>('idle');
9+
const [value, setValue] = useState<T | undefined>(undefined);
10+
const [error, setError] = useState(undefined);
11+
12+
const execute = useCallback(async () => {
13+
setStatus('pending');
14+
setValue(undefined);
15+
16+
try {
17+
const response = await asyncFn();
18+
setValue(response);
19+
setStatus('success');
20+
} catch (error) {
21+
setError(error);
22+
setStatus('error');
23+
}
24+
}, [asyncFn]);
25+
26+
useEffect(() => {
27+
(async () => await execute())();
28+
}, [execute]);
29+
30+
return {status, value, error};
31+
}

Diff for: frontend/src/index.scss

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ $link: $orange;
1111
html, body, #root, .viewport {
1212
width: 100%;
1313
height: 100%;
14+
overflow: auto;
1415
}
1516

1617
body {
@@ -27,3 +28,7 @@ body {
2728
.tag.is-small {
2829
font-size: .5rem;
2930
}
31+
32+
.m-auto {
33+
margin: auto;
34+
}

0 commit comments

Comments
 (0)