diff options
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/Discussions.tsx | 327 |
1 files changed, 215 insertions, 112 deletions
diff --git a/frontend/src/components/Discussions.tsx b/frontend/src/components/Discussions.tsx index 17ae586..62a9fc7 100644 --- a/frontend/src/components/Discussions.tsx +++ b/frontend/src/components/Discussions.tsx | |||
| @@ -1,34 +1,48 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | 2 | ||
| 3 | import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from '@customTypes/Map'; | 3 | import { |
| 4 | import { MapDiscussionCommentContent, MapDiscussionContent } from '@customTypes/Content'; | 4 | MapDiscussion, |
| 5 | import { time_ago } from '@utils/Time'; | 5 | MapDiscussions, |
| 6 | import { API } from '@api/Api'; | 6 | MapDiscussionsDetail, |
| 7 | import "@css/Maps.css" | 7 | } from "@customTypes/Map"; |
| 8 | import { Link } from 'react-router-dom'; | 8 | import { MapDiscussionContent } from "@customTypes/Content"; |
| 9 | import useConfirm from '@hooks/UseConfirm'; | 9 | import { time_ago } from "@utils/Time"; |
| 10 | import { API } from "@api/Api"; | ||
| 11 | import "@css/Maps.css"; | ||
| 12 | import { Link } from "react-router-dom"; | ||
| 13 | import useConfirm from "@hooks/UseConfirm"; | ||
| 10 | 14 | ||
| 11 | interface DiscussionsProps { | 15 | interface DiscussionsProps { |
| 12 | token?: string | 16 | token?: string; |
| 13 | data?: MapDiscussions; | 17 | data?: MapDiscussions; |
| 14 | isModerator: boolean; | 18 | isModerator: boolean; |
| 15 | mapID: string; | 19 | mapID: string; |
| 16 | onRefresh: () => void; | 20 | onRefresh: () => void; |
| 17 | } | 21 | } |
| 18 | 22 | ||
| 19 | const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, mapID, onRefresh }) => { | 23 | const Discussions: React.FC<DiscussionsProps> = ({ |
| 20 | 24 | token, | |
| 25 | data, | ||
| 26 | isModerator, | ||
| 27 | mapID, | ||
| 28 | onRefresh, | ||
| 29 | }) => { | ||
| 21 | const { confirm, ConfirmDialogComponent } = useConfirm(); | 30 | const { confirm, ConfirmDialogComponent } = useConfirm(); |
| 22 | 31 | ||
| 23 | const [discussionThread, setDiscussionThread] = React.useState<MapDiscussion | undefined>(undefined); | 32 | const [discussionThread, setDiscussionThread] = React.useState< |
| 33 | MapDiscussion | undefined | ||
| 34 | >(undefined); | ||
| 24 | const [discussionSearch, setDiscussionSearch] = React.useState<string>(""); | 35 | const [discussionSearch, setDiscussionSearch] = React.useState<string>(""); |
| 25 | 36 | ||
| 26 | const [createDiscussion, setCreateDiscussion] = React.useState<boolean>(false); | 37 | const [createDiscussion, setCreateDiscussion] = |
| 27 | const [createDiscussionContent, setCreateDiscussionContent] = React.useState<MapDiscussionContent>({ | 38 | React.useState<boolean>(false); |
| 28 | title: "", | 39 | const [createDiscussionContent, setCreateDiscussionContent] = |
| 29 | content: "", | 40 | React.useState<MapDiscussionContent>({ |
| 30 | }); | 41 | title: "", |
| 31 | const [createDiscussionCommentContent, setCreateDiscussionCommentContent] = React.useState<string>(""); | 42 | content: "", |
| 43 | }); | ||
| 44 | const [createDiscussionCommentContent, setCreateDiscussionCommentContent] = | ||
| 45 | React.useState<string>(""); | ||
| 32 | 46 | ||
| 33 | const _open_map_discussion = async (discussion_id: number) => { | 47 | const _open_map_discussion = async (discussion_id: number) => { |
| 34 | const mapDiscussion = await API.get_map_discussion(mapID, discussion_id); | 48 | const mapDiscussion = await API.get_map_discussion(mapID, discussion_id); |
| @@ -45,13 +59,23 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map | |||
| 45 | 59 | ||
| 46 | const _create_map_discussion_comment = async (discussion_id: number) => { | 60 | const _create_map_discussion_comment = async (discussion_id: number) => { |
| 47 | if (token) { | 61 | if (token) { |
| 48 | await API.post_map_discussion_comment(token, mapID, discussion_id, createDiscussionCommentContent); | 62 | await API.post_map_discussion_comment( |
| 63 | token, | ||
| 64 | mapID, | ||
| 65 | discussion_id, | ||
| 66 | createDiscussionCommentContent | ||
| 67 | ); | ||
| 49 | await _open_map_discussion(discussion_id); | 68 | await _open_map_discussion(discussion_id); |
| 50 | } | 69 | } |
| 51 | }; | 70 | }; |
| 52 | 71 | ||
| 53 | const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => { | 72 | const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => { |
| 54 | if (await confirm("Delete Map Discussion", `Are you sure you want to remove post: ${discussion.title}?`)) { | 73 | if ( |
| 74 | await confirm( | ||
| 75 | "Delete Map Discussion", | ||
| 76 | `Are you sure you want to remove post: ${discussion.title}?` | ||
| 77 | ) | ||
| 78 | ) { | ||
| 55 | if (token) { | 79 | if (token) { |
| 56 | await API.delete_map_discussion(token, mapID, discussion.id); | 80 | await API.delete_map_discussion(token, mapID, discussion.id); |
| 57 | onRefresh(); | 81 | onRefresh(); |
| @@ -60,107 +84,186 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map | |||
| 60 | }; | 84 | }; |
| 61 | 85 | ||
| 62 | return ( | 86 | return ( |
| 63 | <section id='section7' className='summary3'> | 87 | <section id="section7" className="summary3"> |
| 64 | {ConfirmDialogComponent} | 88 | {ConfirmDialogComponent} |
| 65 | <div id='discussion-search'> | 89 | <div id="discussion-search"> |
| 66 | <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={(e) => setDiscussionSearch(e.target.value)} /> | 90 | <input |
| 67 | <div><button onClick={() => setCreateDiscussion(true)}>New Post</button></div> | 91 | type="text" |
| 92 | value={discussionSearch} | ||
| 93 | placeholder={"Search for posts..."} | ||
| 94 | onChange={(e) => setDiscussionSearch(e.target.value)} | ||
| 95 | /> | ||
| 96 | <div> | ||
| 97 | <button onClick={() => setCreateDiscussion(true)}>New Post</button> | ||
| 98 | </div> | ||
| 68 | </div> | 99 | </div> |
| 69 | 100 | ||
| 70 | { // janky ternary operators here, could divide them to more components? | 101 | { |
| 71 | createDiscussion ? | 102 | // janky ternary operators here, could divide them to more components? |
| 72 | ( | 103 | createDiscussion ? ( |
| 73 | <div id='discussion-create'> | 104 | <div id="discussion-create"> |
| 74 | <span>Create Post</span> | 105 | <span>Create Post</span> |
| 75 | <button onClick={() => setCreateDiscussion(false)}>X</button> | 106 | <button onClick={() => setCreateDiscussion(false)}>X</button> |
| 76 | <div style={{ gridColumn: "1 / span 2" }}> | 107 | <div style={{ gridColumn: "1 / span 2" }}> |
| 77 | <input id='discussion-create-title' placeholder='Title...' onChange={(e) => setCreateDiscussionContent({ | 108 | <input |
| 78 | ...createDiscussionContent, | 109 | id="discussion-create-title" |
| 79 | title: e.target.value, | 110 | placeholder="Title..." |
| 80 | })} /> | 111 | onChange={(e) => |
| 81 | <input id='discussion-create-content' placeholder='Enter the content...' onChange={(e) => setCreateDiscussionContent({ | 112 | setCreateDiscussionContent({ |
| 82 | ...createDiscussionContent, | 113 | ...createDiscussionContent, |
| 83 | content: e.target.value, | 114 | title: e.target.value, |
| 84 | })} /> | 115 | }) |
| 85 | </div> | 116 | } |
| 86 | <div style={{ placeItems: "end", gridColumn: "1 / span 2" }}> | 117 | /> |
| 87 | <button id='discussion-create-button' onClick={() => _create_map_discussion()}>Post</button> | 118 | <input |
| 88 | </div> | 119 | id="discussion-create-content" |
| 120 | placeholder="Enter the content..." | ||
| 121 | onChange={(e) => | ||
| 122 | setCreateDiscussionContent({ | ||
| 123 | ...createDiscussionContent, | ||
| 124 | content: e.target.value, | ||
| 125 | }) | ||
| 126 | } | ||
| 127 | /> | ||
| 128 | </div> | ||
| 129 | <div style={{ placeItems: "end", gridColumn: "1 / span 2" }}> | ||
| 130 | <button | ||
| 131 | id="discussion-create-button" | ||
| 132 | onClick={() => _create_map_discussion()} | ||
| 133 | > | ||
| 134 | Post | ||
| 135 | </button> | ||
| 136 | </div> | ||
| 137 | </div> | ||
| 138 | ) : discussionThread ? ( | ||
| 139 | <div id="discussion-thread"> | ||
| 140 | <div> | ||
| 141 | <span>{discussionThread.discussion.title}</span> | ||
| 142 | <button onClick={() => setDiscussionThread(undefined)}>X</button> | ||
| 89 | </div> | 143 | </div> |
| 90 | ) | ||
| 91 | : | ||
| 92 | discussionThread ? | ||
| 93 | ( | ||
| 94 | <div id='discussion-thread'> | ||
| 95 | <div> | ||
| 96 | <span>{discussionThread.discussion.title}</span> | ||
| 97 | <button onClick={() => setDiscussionThread(undefined)}>X</button> | ||
| 98 | </div> | ||
| 99 | 144 | ||
| 100 | <div> | 145 | <div> |
| 101 | <Link to={`/users/${discussionThread.discussion.creator.steam_id}`}> | 146 | <Link |
| 102 | <img src={discussionThread.discussion.creator.avatar_link} alt="" /> | 147 | to={`/users/${discussionThread.discussion.creator.steam_id}`} |
| 103 | </Link> | 148 | > |
| 104 | <div> | 149 | <img |
| 105 | <span>{discussionThread.discussion.creator.user_name}</span> | 150 | src={discussionThread.discussion.creator.avatar_link} |
| 106 | <span>{time_ago(new Date(discussionThread.discussion.created_at.replace("T", " ").replace("Z", "")))}</span> | 151 | alt="" |
| 107 | <span>{discussionThread.discussion.content}</span> | 152 | /> |
| 108 | </div> | 153 | </Link> |
| 109 | {discussionThread.discussion.comments ? | 154 | <div> |
| 110 | discussionThread.discussion.comments.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) | 155 | <span>{discussionThread.discussion.creator.user_name}</span> |
| 111 | .map(e => ( | 156 | <span> |
| 112 | <> | 157 | {time_ago( |
| 113 | <Link to={`/users/${e.user.steam_id}`}> | 158 | new Date( |
| 114 | <img src={e.user.avatar_link} alt="" /> | 159 | discussionThread.discussion.created_at |
| 115 | </Link> | 160 | .replace("T", " ") |
| 116 | <div> | 161 | .replace("Z", "") |
| 117 | <span>{e.user.user_name}</span> | 162 | ) |
| 118 | <span>{time_ago(new Date(e.date.replace("T", " ").replace("Z", "")))}</span> | 163 | )} |
| 119 | <span>{e.comment}</span> | 164 | </span> |
| 120 | </div> | 165 | <span>{discussionThread.discussion.content}</span> |
| 121 | </> | 166 | </div> |
| 122 | )) : "" | 167 | {discussionThread.discussion.comments |
| 123 | } | 168 | ? discussionThread.discussion.comments |
| 124 | </div> | 169 | .sort( |
| 125 | <div id='discussion-send'> | 170 | (a, b) => |
| 126 | <input type="text" value={createDiscussionCommentContent} placeholder={"Message"} | 171 | new Date(a.date).getTime() - new Date(b.date).getTime() |
| 127 | onKeyDown={(e) => e.key === "Enter" && _create_map_discussion_comment(discussionThread.discussion.id)} | 172 | ) |
| 128 | onChange={(e) => setCreateDiscussionCommentContent(e.target.value)} /> | 173 | .map((e) => ( |
| 129 | <div><button onClick={() => { | 174 | <> |
| 175 | <Link to={`/users/${e.user.steam_id}`}> | ||
| 176 | <img src={e.user.avatar_link} alt="" /> | ||
| 177 | </Link> | ||
| 178 | <div> | ||
| 179 | <span>{e.user.user_name}</span> | ||
| 180 | <span> | ||
| 181 | {time_ago( | ||
| 182 | new Date( | ||
| 183 | e.date.replace("T", " ").replace("Z", "") | ||
| 184 | ) | ||
| 185 | )} | ||
| 186 | </span> | ||
| 187 | <span>{e.comment}</span> | ||
| 188 | </div> | ||
| 189 | </> | ||
| 190 | )) | ||
| 191 | : ""} | ||
| 192 | </div> | ||
| 193 | <div id="discussion-send"> | ||
| 194 | <input | ||
| 195 | type="text" | ||
| 196 | value={createDiscussionCommentContent} | ||
| 197 | placeholder={"Message"} | ||
| 198 | onKeyDown={(e) => | ||
| 199 | e.key === "Enter" && | ||
| 200 | _create_map_discussion_comment(discussionThread.discussion.id) | ||
| 201 | } | ||
| 202 | onChange={(e) => | ||
| 203 | setCreateDiscussionCommentContent(e.target.value) | ||
| 204 | } | ||
| 205 | /> | ||
| 206 | <div> | ||
| 207 | <button | ||
| 208 | onClick={() => { | ||
| 130 | if (createDiscussionCommentContent !== "") { | 209 | if (createDiscussionCommentContent !== "") { |
| 131 | _create_map_discussion_comment(discussionThread.discussion.id); | 210 | _create_map_discussion_comment( |
| 211 | discussionThread.discussion.id | ||
| 212 | ); | ||
| 132 | setCreateDiscussionCommentContent(""); | 213 | setCreateDiscussionCommentContent(""); |
| 133 | } | 214 | } |
| 134 | }}>Send</button></div> | 215 | }} |
| 135 | </div> | 216 | > |
| 136 | 217 | Send | |
| 218 | </button> | ||
| 137 | </div> | 219 | </div> |
| 138 | ) | 220 | </div> |
| 139 | : | 221 | </div> |
| 140 | ( | 222 | ) : data ? ( |
| 141 | data ? | 223 | <> |
| 142 | (<> | 224 | {data.discussions |
| 143 | {data.discussions.filter(f => f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) | 225 | .filter((f) => f.title.includes(discussionSearch)) |
| 144 | .map((e, i) => ( | 226 | .sort( |
| 145 | <div id='discussion-post'> | 227 | (a, b) => |
| 146 | <button key={e.id} onClick={() => _open_map_discussion(e.id)}> | 228 | new Date(b.updated_at).getTime() - |
| 147 | <span>{e.title}</span> | 229 | new Date(a.updated_at).getTime() |
| 148 | {isModerator ? | 230 | ) |
| 149 | <button onClick={(m) => { | 231 | .map((e, i) => ( |
| 150 | m.stopPropagation(); | 232 | <div id="discussion-post"> |
| 151 | _delete_map_discussion(e); | 233 | <button key={e.id} onClick={() => _open_map_discussion(e.id)}> |
| 152 | }}>Delete Post</button> | 234 | <span>{e.title}</span> |
| 153 | : <span></span> | 235 | {isModerator ? ( |
| 154 | } | 236 | <button |
| 155 | <span><b>{e.creator.user_name}:</b> {e.content}</span> | 237 | onClick={(m) => { |
| 156 | <span>Last Updated: {time_ago(new Date(e.updated_at.replace("T", " ").replace("Z", "")))}</span> | 238 | m.stopPropagation(); |
| 157 | </button> | 239 | _delete_map_discussion(e); |
| 158 | </div> | 240 | }} |
| 159 | ))} | 241 | > |
| 160 | </>) | 242 | Delete Post |
| 161 | : | 243 | </button> |
| 162 | (<span style={{ textAlign: "center", display: "block" }}>No Discussions...</span>) | 244 | ) : ( |
| 163 | ) | 245 | <span></span> |
| 246 | )} | ||
| 247 | <span> | ||
| 248 | <b>{e.creator.user_name}:</b> {e.content} | ||
| 249 | </span> | ||
| 250 | <span> | ||
| 251 | Last Updated:{" "} | ||
| 252 | {time_ago( | ||
| 253 | new Date( | ||
| 254 | e.updated_at.replace("T", " ").replace("Z", "") | ||
| 255 | ) | ||
| 256 | )} | ||
| 257 | </span> | ||
| 258 | </button> | ||
| 259 | </div> | ||
| 260 | ))} | ||
| 261 | </> | ||
| 262 | ) : ( | ||
| 263 | <span style={{ textAlign: "center", display: "block" }}> | ||
| 264 | No Discussions... | ||
| 265 | </span> | ||
| 266 | ) | ||
| 164 | } | 267 | } |
| 165 | </section> | 268 | </section> |
| 166 | ); | 269 | ); |