diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2024-09-03 00:08:53 +0300 |
|---|---|---|
| committer | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2024-09-03 00:08:53 +0300 |
| commit | a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98 (patch) | |
| tree | edf8630e9d6426124dd49854af0cb703ebc5b710 /frontend/src/components/Discussions.tsx | |
| parent | fix: revert to static homepage (#195) (diff) | |
| download | lphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.tar.gz lphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.tar.bz2 lphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.zip | |
refactor: port to typescript
Diffstat (limited to 'frontend/src/components/Discussions.tsx')
| -rw-r--r-- | frontend/src/components/Discussions.tsx | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/frontend/src/components/Discussions.tsx b/frontend/src/components/Discussions.tsx new file mode 100644 index 0000000..1cd3523 --- /dev/null +++ b/frontend/src/components/Discussions.tsx | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | import React from 'react'; | ||
| 2 | |||
| 3 | import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from '../types/Map'; | ||
| 4 | import { MapDiscussionCommentContent, MapDiscussionContent } from '../types/Content'; | ||
| 5 | import { time_ago } from '../utils/Time'; | ||
| 6 | import { API } from '../api/Api'; | ||
| 7 | import "../css/Maps.css" | ||
| 8 | |||
| 9 | interface DiscussionsProps { | ||
| 10 | data?: MapDiscussions; | ||
| 11 | isModerator: boolean; | ||
| 12 | mapID: string; | ||
| 13 | onRefresh: () => void; | ||
| 14 | } | ||
| 15 | |||
| 16 | const Discussions: React.FC<DiscussionsProps> = ({ data, isModerator, mapID, onRefresh }) => { | ||
| 17 | |||
| 18 | const [discussionThread, setDiscussionThread] = React.useState<MapDiscussion | undefined>(undefined); | ||
| 19 | const [discussionSearch, setDiscussionSearch] = React.useState<string>(""); | ||
| 20 | |||
| 21 | const [createDiscussion, setCreateDiscussion] = React.useState<boolean>(false); | ||
| 22 | const [createDiscussionContent, setCreateDiscussionContent] = React.useState<MapDiscussionContent>({ | ||
| 23 | title: "", | ||
| 24 | content: "", | ||
| 25 | }); | ||
| 26 | const [createDiscussionCommentContent, setCreateDiscussionCommentContent] = React.useState<MapDiscussionCommentContent>({ | ||
| 27 | comment: "", | ||
| 28 | }); | ||
| 29 | |||
| 30 | const _open_map_discussion = async (discussion_id: number) => { | ||
| 31 | const mapDiscussion = await API.get_map_discussion(mapID, discussion_id); | ||
| 32 | setDiscussionThread(mapDiscussion); | ||
| 33 | }; | ||
| 34 | |||
| 35 | const _create_map_discussion = async () => { | ||
| 36 | await API.post_map_discussion(mapID, createDiscussionContent); | ||
| 37 | setCreateDiscussion(false); | ||
| 38 | onRefresh(); | ||
| 39 | }; | ||
| 40 | |||
| 41 | const _create_map_discussion_comment = async (discussion_id: number) => { | ||
| 42 | await API.post_map_discussion_comment(mapID, discussion_id, createDiscussionCommentContent); | ||
| 43 | await _open_map_discussion(discussion_id); | ||
| 44 | }; | ||
| 45 | |||
| 46 | const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => { | ||
| 47 | if (window.confirm(`Are you sure you want to remove post: ${discussion.title}?`)) { | ||
| 48 | await API.delete_map_discussion(mapID, discussion.id); | ||
| 49 | onRefresh(); | ||
| 50 | } | ||
| 51 | }; | ||
| 52 | |||
| 53 | return ( | ||
| 54 | <section id='section7' className='summary3'> | ||
| 55 | <div id='discussion-search'> | ||
| 56 | <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={(e) => setDiscussionSearch(e.target.value)} /> | ||
| 57 | <div><button onClick={() => setCreateDiscussion(true)}>New Post</button></div> | ||
| 58 | </div> | ||
| 59 | |||
| 60 | { // janky ternary operators here, could divide them to more components? | ||
| 61 | createDiscussion ? | ||
| 62 | ( | ||
| 63 | <div id='discussion-create'> | ||
| 64 | <span>Create Post</span> | ||
| 65 | <button onClick={() => setCreateDiscussion(false)}>X</button> | ||
| 66 | <div style={{ gridColumn: "1 / span 2" }}> | ||
| 67 | <input id='discussion-create-title' placeholder='Title...' onChange={(e) => setCreateDiscussionContent({ | ||
| 68 | ...createDiscussionContent, | ||
| 69 | title: e.target.value, | ||
| 70 | })} /> | ||
| 71 | <input id='discussion-create-content' placeholder='Enter the comment...' onChange={(e) => setCreateDiscussionContent({ | ||
| 72 | ...createDiscussionContent, | ||
| 73 | title: e.target.value, | ||
| 74 | })} /> | ||
| 75 | </div> | ||
| 76 | <div style={{ placeItems: "end", gridColumn: "1 / span 2" }}> | ||
| 77 | <button id='discussion-create-button' onClick={() => _create_map_discussion()}>Post</button> | ||
| 78 | </div> | ||
| 79 | </div> | ||
| 80 | ) | ||
| 81 | : | ||
| 82 | discussionThread ? | ||
| 83 | ( | ||
| 84 | <div id='discussion-thread'> | ||
| 85 | <div> | ||
| 86 | <span>{discussionThread.discussion.title}</span> | ||
| 87 | <button onClick={() => setDiscussionThread(undefined)}>X</button> | ||
| 88 | </div> | ||
| 89 | |||
| 90 | <div> | ||
| 91 | <img src={discussionThread.discussion.creator.avatar_link} alt="" /> | ||
| 92 | <div> | ||
| 93 | <span>{discussionThread.discussion.creator.user_name}</span> | ||
| 94 | <span>{time_ago(new Date(discussionThread.discussion.created_at.replace("T", " ").replace("Z", "")))}</span> | ||
| 95 | <span>{discussionThread.discussion.content}</span> | ||
| 96 | </div> | ||
| 97 | {discussionThread.discussion.comments ? | ||
| 98 | discussionThread.discussion.comments.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) | ||
| 99 | .map(e => ( | ||
| 100 | <> | ||
| 101 | <img src={e.user.avatar_link} alt="" /> | ||
| 102 | <div> | ||
| 103 | <span>{e.user.user_name}</span> | ||
| 104 | <span>{time_ago(new Date(e.date.replace("T", " ").replace("Z", "")))}</span> | ||
| 105 | <span>{e.comment}</span> | ||
| 106 | </div> | ||
| 107 | </> | ||
| 108 | )) : "" | ||
| 109 | } | ||
| 110 | </div> | ||
| 111 | <div id='discussion-send'> | ||
| 112 | <input type="text" placeholder={"Message"} onKeyDown={(e) => e.key === "Enter" && _create_map_discussion_comment(discussionThread.discussion.id)} onChange={(e) => setCreateDiscussionCommentContent({ | ||
| 113 | ...createDiscussionContent, | ||
| 114 | comment: e.target.value, | ||
| 115 | })} /> | ||
| 116 | <div><button onClick={() => _create_map_discussion_comment(discussionThread.discussion.id)}>Send</button></div> | ||
| 117 | </div> | ||
| 118 | |||
| 119 | </div> | ||
| 120 | ) | ||
| 121 | : | ||
| 122 | ( | ||
| 123 | data ? | ||
| 124 | (<> | ||
| 125 | {data.discussions.filter(f => f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) | ||
| 126 | .map((e, i) => ( | ||
| 127 | <div id='discussion-post'> | ||
| 128 | <button key={e.id} onClick={() => _open_map_discussion(e.id)}> | ||
| 129 | <span>{e.title}</span> | ||
| 130 | {isModerator ? | ||
| 131 | <button onClick={(m) => { | ||
| 132 | m.stopPropagation(); | ||
| 133 | _delete_map_discussion(e); | ||
| 134 | }}>Delete Post</button> | ||
| 135 | : <span></span> | ||
| 136 | } | ||
| 137 | <span><b>{e.creator.user_name}:</b> {e.content}</span> | ||
| 138 | <span>Last Updated: {time_ago(new Date(e.updated_at.replace("T", " ").replace("Z", "")))}</span> | ||
| 139 | </button> | ||
| 140 | </div> | ||
| 141 | ))} | ||
| 142 | </>) | ||
| 143 | : | ||
| 144 | (<span style={{ textAlign: "center", display: "block" }}>No Discussions...</span>) | ||
| 145 | ) | ||
| 146 | } | ||
| 147 | </section> | ||
| 148 | ); | ||
| 149 | }; | ||
| 150 | |||
| 151 | export default Discussions; | ||