aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorNidboj132 <28981031+Nidboj132@users.noreply.github.com>2023-12-23 14:09:35 +0100
committerGitHub <noreply@github.com>2023-12-23 14:09:35 +0100
commit62c116189a1b66667f5843e3a069add05c2487f8 (patch)
treef051e5937b781ae3d67ef9854c2b3285158c3270 /frontend
parentfix: sql typo discussions delete (#142) (diff)
downloadlphub-62c116189a1b66667f5843e3a069add05c2487f8.tar.gz
lphub-62c116189a1b66667f5843e3a069add05c2487f8.tar.bz2
lphub-62c116189a1b66667f5843e3a069add05c2487f8.zip
feat: discussion page (#137)
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/pages/profile.css5
-rw-r--r--frontend/src/components/pages/profile.js4
-rw-r--r--frontend/src/components/pages/summary.css226
-rw-r--r--frontend/src/components/pages/summary.js176
4 files changed, 406 insertions, 5 deletions
diff --git a/frontend/src/components/pages/profile.css b/frontend/src/components/pages/profile.css
index 677fa9b..4944ade 100644
--- a/frontend/src/components/pages/profile.css
+++ b/frontend/src/components/pages/profile.css
@@ -174,7 +174,7 @@ span.titles{
174 height: 34px; 174 height: 34px;
175 display: grid; 175 display: grid;
176 font-size: 20px; 176 font-size: 20px;
177 padding-left: 60px; 177 padding-left: 40px;
178 margin: 0 20px; 178 margin: 0 20px;
179 grid-template-columns: 15% 15% 5% 15% 5% 15% 15% 15%; 179 grid-template-columns: 15% 15% 5% 15% 5% 15% 15% 15%;
180} 180}
@@ -194,7 +194,7 @@ span.titles{
194 height: 44px; 194 height: 44px;
195 195
196 border-radius: 20px; 196 border-radius: 20px;
197 padding: 0 0 0 60px; 197 padding: 0 0 0 40px;
198 font-size: 20px; 198 font-size: 20px;
199 199
200 color: inherit; 200 color: inherit;
@@ -205,6 +205,7 @@ span.titles{
205 display: grid; 205 display: grid;
206 grid-template-columns: 15% 15% 5% 15% 5% 15% 15% 15%; 206 grid-template-columns: 15% 15% 5% 15% 5% 15% 15% 15%;
207 overflow: hidden; 207 overflow: hidden;
208 white-space: nowrap;
208 209
209 transition: height .2s 210 transition: height .2s
210} 211}
diff --git a/frontend/src/components/pages/profile.js b/frontend/src/components/pages/profile.js
index 041f585..7c45320 100644
--- a/frontend/src/components/pages/profile.js
+++ b/frontend/src/components/pages/profile.js
@@ -129,7 +129,7 @@ function UpdateProfile(){
129 method: 'POST', 129 method: 'POST',
130 headers: {Authorization: token} 130 headers: {Authorization: token}
131 }).then(r=>r.json()) 131 }).then(r=>r.json())
132 .then(d=>d.success?window.location.reload():window.alert(`Error: ${d.message}`)) 132 .then(d=>d.success?window.alert("profile updated"):window.alert(`Error: ${d.message}`))
133} 133}
134 134
135function TimeAgo(date) { 135function TimeAgo(date) {
@@ -348,7 +348,7 @@ return (
348 <span>{r.name}</span> 348 <span>{r.name}</span>
349 <span style={{ display: "grid" }}>{record.scores[i].score_count}</span> 349 <span style={{ display: "grid" }}>{record.scores[i].score_count}</span>
350 <span style={{ display: "grid" }}>{record.scores[i].score_count-record.map_wr_count}</span> 350 <span style={{ display: "grid" }}>{record.scores[i].score_count-record.map_wr_count}</span>
351 <span>{TicksToTime(record.scores[i].score_time)}</span> 351 <span style={{ display: "grid" }}>{TicksToTime(record.scores[i].score_time)}</span>
352 <span> </span> 352 <span> </span>
353 {i===0?<span>#{record.placement}</span>:<span> </span>} 353 {i===0?<span>#{record.placement}</span>:<span> </span>}
354 <span>{record.scores[i].date.split("T")[0]}</span> 354 <span>{record.scores[i].date.split("T")[0]}</span>
diff --git a/frontend/src/components/pages/summary.css b/frontend/src/components/pages/summary.css
index 47c3f4b..c99a0bf 100644
--- a/frontend/src/components/pages/summary.css
+++ b/frontend/src/components/pages/summary.css
@@ -449,7 +449,233 @@ text-align: center;
449.leaderboard-record:last-child{margin: 10px 20px 10px 20px;} 449.leaderboard-record:last-child{margin: 10px 20px 10px 20px;}
450 450
451 451
452#section7{
453 margin: 40px 0 20px 0;
454 background-color: #202232;
455 border-radius: 24px;
456 padding: 10px;
457}
458
459#discussion-search{
460 height: 46px;
461 width: 100%;
462 display: grid;
463 grid-template-columns: 1fr 100px;
464 margin: 0 0 20px 0;
465}
466#discussion-search>input::placeholder{color: #aaa;}
467#discussion-search>input{
468 background-color: #2b2e46;
469 font-size: 20px;
470 padding-left: 10px;
471 color: white;
472 border: 0;
473 border-radius: 16px 0 0 16px;
474 font-family: inherit;
475}
476#discussion-search>div>button:hover{filter: brightness(75%);}
477#discussion-search>div>button{
478 padding: 7px 16px;
479 margin: 8px 0;
480 border: 0;
481 font-size: 16px;
482 border-radius: 24px;
483 display: block;
484 background-color:#3c91e6;
485 font-family: inherit;
486 font-weight: bold;
487 cursor: pointer;
488 color: white;
489
490 transition: filter .2s;
491}
492#discussion-search>div{
493 background-color: #2b2e46;
494 border-radius: 0 16px 16px 0;
495}
496#discussion-post>button:nth-child(1)>span>b{font-size: 18px;color:#cdcfdf;font-weight: lighter;}
497#discussion-post>button:nth-child(1){
498 background-color: #2b2e46;
499 display: grid;
500 grid-template-columns: minmax(0, 1fr) 150px;
501
502 border-radius: 16px;
503 padding: 16px 12px;
504 margin: 8px 0 0 0;
505 border: 0;
506 width: 100%; height: 100px;
507 text-align: start;
508 white-space: nowrap;
509 color: #cdcfdf;
510 cursor: pointer;
511 overflow: hidden;
512}
513#discussion-post>button:nth-child(1)>span:nth-child(1){font-size: 32px;}
514#discussion-post>button:nth-child(1)>span:nth-child(3){color: #aaa; font-size: 18px;}
515#discussion-post>button:nth-child(1)>span:nth-child(4){
516 opacity: .7;
517 height: 40px;
518 display: flex;
519 place-items: end;
520 justify-content: end;
521}
522
523#discussion-post{height: 100px;}
524#discussion-post>button>button:hover{filter: brightness(75%); }
525#discussion-post>button>button{
526 padding: 7px 16px;
527
528 border: 0;
529 font-size: 16px;
530 border-radius: 24px;
531 background-color:#e52d04;
532 font-family: BarlowSemiCondensed-Regular;
533 font-weight: bold;
534 cursor: pointer;
535 color: white;
536
537 transition: filter .2s;
538}
539
540
541#discussion-create>div{
542 display: grid;
543 text-align: start;
544}
545#discussion-create{
546 display: grid;
547 grid-template-columns: 1fr 40px;
548 height: auto;
549 word-wrap: break-word;
550}
551
552#discussion-create>span{padding-left: 20px;}
553#discussion-create>div>input::placeholder{color: #aaa;}
554#discussion-create>div>input{
555 background-color: #2b2e46;
556 font-size: 20px;
557 padding-left: 10px;
558 margin-top: 10px;
559 height: 32px;
560 color: white;
561 border: 0;
562 font-family: inherit;
563}
564#discussion-create>div>input:nth-child(2){font-size: 16px;}
452 565
566#discussion-create-button:hover{filter: brightness(75%);}
567#discussion-create-button{
568 padding: 7px 16px;
569 margin: 8px 0 0 0;
570 border: 0;
571 font-size: 16px;
572 border-radius: 24px;
573
574 background-color:#3c91e6;
575 font-family: inherit;
576 font-weight: bold;
577 cursor: pointer;
578 color: white;
579 width: min-content;
580 grid-column: 1 / span 2;
581
582
583 transition: filter .2s;
584}
585
586
587#discussion-thread>div:nth-child(1){
588 display: grid;
589 grid-template-columns: 1fr 40px;
590 height: auto;
591 padding: 0 0 10px 20px;
592 word-wrap: break-word;
593}
594
595#discussion-create>button:nth-child(2),
596#discussion-thread>div>button{
597 height: 40px;
598 float:inline-end;
599 color:#cdcfdf;
600 background-color: #0000;
601 border: 0;
602 font-size: 38px;
603 cursor: pointer;
604}
605
606
607#discussion-thread>div:nth-child(2)>img{
608 width: 60px; height: 60px;
609 border-radius: 100px;
610 margin: 20px 0 0 0;
611}
612#discussion-thread>div:nth-child(2)>div{
613 height: max-content;
614 padding: 20px 0 0 10px;
615 display: inline-grid;
616 grid-template-columns: min-content 1fr ;
617 overflow: hidden;
618
619}
620#discussion-thread>div:nth-child(2)>div>span:nth-child(1){font-weight: bold;height: 30px;}
621#discussion-thread>div:nth-child(2)>div>span:nth-child(2){
622 opacity: 0.6;
623 height: 30px;
624 font-size: 80%;
625 padding-left: 10px;
626}
627#discussion-thread>div:nth-child(2)>div>span:nth-child(3){
628 grid-column: 1 / span 2;
629 height: max-content;
630 word-wrap: break-word;
631}
632#discussion-thread>div:nth-child(2){
633 display: grid;
634 grid-template-columns: 60px 1fr;
635 font-size: 20px;
636 max-height: 522px;
637 overflow-y: auto;
638}
639
640
641#discussion-send{
642 height: 48px;
643 width: 100%;
644 display: grid;
645 grid-template-columns: 1fr 80px;
646 margin: 10px 0 0 0;
647}
648#discussion-send>input::placeholder{color: #aaa;}
649#discussion-send>input{
650 background-color: #2b2e46;
651 padding-left: 10px;
652 color: white;
653 border: 0;
654 font-size: 20px;
655 border-radius: 16px 0 0 16px;
656 font-family: inherit;
657}
658#discussion-send>div{
659 background-color: #2b2e46;
660 border-radius: 0 16px 16px 0;
661
662}
663#discussion-send>div>button:hover{ filter: brightness(75%);}
664#discussion-send>div>button{
665 padding: 7px 20px;
666 margin: 8px 0;
667 font-size: 16px;
668 border: 0;
669 border-radius: 24px;
670 display: block;
671 background-color:#3c91e6;
672 font-family: inherit;
673 font-weight: bold;
674 cursor: pointer;
675 color: white;
676
677 transition: filter .2s;
678}
453 679
454 680
455 681
diff --git a/frontend/src/components/pages/summary.js b/frontend/src/components/pages/summary.js
index 7c72c48..228ce01 100644
--- a/frontend/src/components/pages/summary.js
+++ b/frontend/src/components/pages/summary.js
@@ -21,6 +21,10 @@ const fakedata={} //for debug
21 const [data, setData] = React.useState(null); 21 const [data, setData] = React.useState(null);
22 React.useEffect(() => { 22 React.useEffect(() => {
23 setData(null) 23 setData(null)
24 setDiscussionThread(null)
25 setCreatePostState(0)
26 setSelectedRun(0)
27 setCatState(1)
24 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/summary`) 28 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/summary`)
25 .then(r => r.json()) 29 .then(r => r.json())
26 .then(d => { 30 .then(d => {
@@ -40,6 +44,28 @@ const fakedata={} //for debug
40 // eslint-disable-next-line 44 // eslint-disable-next-line
41 }, [pageNumber,location.pathname]); 45 }, [pageNumber,location.pathname]);
42 46
47 const [discussions,setDiscussions] = React.useState(null)
48 function fetchDiscussions() {
49 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/discussions`)
50 .then(r=>r.json())
51 .then(d=>setDiscussions(d.data.discussions))
52 }
53
54 React.useEffect(()=>{
55 fetchDiscussions()
56 },[location.pathname])
57
58
59
60const [discussionThread,setDiscussionThread] = React.useState(null)
61function openDiscussion(x){
62 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/discussions/${x}`)
63 .then(r=>r.json())
64 .then(d=>setDiscussionThread(d.data.discussion))
65}
66const [discussionSearch, setDiscussionSearch] = React.useState("")
67
68
43 69
44 70
45const [navState, setNavState] = React.useState(0); // eslint-disable-next-line 71const [navState, setNavState] = React.useState(0); // eslint-disable-next-line
@@ -204,7 +230,9 @@ function YouTubeGetID(url){
204 } 230 }
205 231
206function TimeAgo(date) { 232function TimeAgo(date) {
207 const seconds = Math.floor((new Date() - date) / 1000); 233 // const seconds = Math.floor((new Date() - date) / 1000);
234
235 const seconds = Math.floor(((new Date(new Date() - (date.getTimezoneOffset()*-60000))) - date) / 1000);
208 236
209 let interval = Math.floor(seconds / 31536000); 237 let interval = Math.floor(seconds / 31536000);
210 if (interval > 1) {return interval + ' years ago';} 238 if (interval > 1) {return interval + ' years ago';}
@@ -239,6 +267,52 @@ function TicksToTime(ticks) {
239 return `${hours===0?"":hours+":"}${minutes===0?"":hours>0?minutes.toString().padStart(2, '0')+":":(minutes+":")}${minutes>0?seconds.toString().padStart(2, '0'):seconds}.${milliseconds.toString().padStart(3, '0')} (${ticks})`; 267 return `${hours===0?"":hours+":"}${minutes===0?"":hours>0?minutes.toString().padStart(2, '0')+":":(minutes+":")}${minutes>0?seconds.toString().padStart(2, '0'):seconds}.${milliseconds.toString().padStart(3, '0')} (${ticks})`;
240} 268}
241 269
270function PostComment() {
271
272 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/discussions/${discussionThread.id}`,{
273 method:"POST",
274 headers:{authorization:token},
275 body:JSON.stringify({"comment":document.querySelector("#discussion-send>input").value})
276})
277.then(r=>r.json())
278.then(d=>{
279 document.querySelector("#discussion-send>input").value=""
280 openDiscussion(discussionThread.id)
281})
282}
283
284
285const [createPostState,setCreatePostState] = React.useState(0)
286function CreatePost() {
287
288 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/discussions`,{
289 method:"POST",
290 headers:{authorization:token},
291 body:JSON.stringify({"title":document.querySelector("#discussion-create-title").value,"content":document.querySelector("#discussion-create-content").value})
292 })
293 .then(r=>r.json())
294 .then(d=>{
295 setCreatePostState(0)
296 fetchDiscussions()
297 })
298}
299
300function DeletePost(post) {
301if(window.confirm(`Are you sure you want to remove post: ${post.title}?`)){
302 console.log("deleted",post.id)
303 fetch(`https://lp.ardapektezol.com/api/v1/maps/${location.pathname.split('/')[2]}/discussions/${post.id}`,{
304 method:"DELETE",
305 headers:{authorization:token},
306 })
307 .then(r=>r.json())
308 .then(d=>{
309 fetchDiscussions()
310 })
311}
312}
313
314
315
242if(data!==null){ 316if(data!==null){
243return ( 317return (
244 <> 318 <>
@@ -365,6 +439,8 @@ return (
365 </div> 439 </div>
366 </section> 440 </section>
367 441
442 {/* Leaderboards */}
443
368 {lbData===null?"":lbData.success===false?( 444 {lbData===null?"":lbData.success===false?(
369 <section id='section6' className='summary2'> 445 <section id='section6' className='summary2'>
370 <h1 style={{textAlign:"center"}}>Map is not available for competitive boards.</h1> 446 <h1 style={{textAlign:"center"}}>Map is not available for competitive boards.</h1>
@@ -445,6 +521,104 @@ return (
445 </section> 521 </section>
446 )} 522 )}
447 523
524
525 {/* Discussions */}
526 <section id='section7' className='summary3'>
527
528 {discussionThread === null ? (
529 createPostState === 0 ? (
530 discussions !== null ? (
531 // Main screen
532 <>
533 <div id='discussion-search'>
534 <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={()=>setDiscussionSearch(document.querySelector("#discussion-search>input").value)} />
535 <div><button onClick={()=>setCreatePostState(1)}>New Post</button></div>
536 </div>
537 {discussions.filter(f=>f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))
538 .map((e, i) => (
539 <div id='discussion-post'>
540
541 <button key={e.id} onClick={() => openDiscussion(e.id)}>
542 <span>{e.title}</span>
543
544 {token!==null?e.creator.steam_id===JSON.parse(atob(token.split(".")[1])).sub?
545 <button onClick={()=>DeletePost(e)}>Delete Post</button>
546 :<span></span>:<span></span>}
547 <span><b>{e.creator.user_name}:</b> {e.content}</span>
548 <span>last updated: {TimeAgo(new Date(e.updated_at.replace("T"," ").replace("Z","")))}</span>
549 </button>
550 </div>
551 ))}
552 </>
553 ):(
554
555 // Main screen (no posts)
556 <>
557 <div id='discussion-search'>
558 <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={()=>setDiscussionSearch(document.querySelector("#discussion-search>input").value)} />
559 <div><button onClick={()=>setCreatePostState(1)}>New Post</button></div>
560 </div>
561 <span style={{textAlign:"center",display:"block"}}>no discussions</span>
562</>
563 )
564 ):(
565 // Creating post
566 <div id='discussion-create'>
567 <span>Create post</span>
568 <button onClick={()=>setCreatePostState(0)}>X</button>
569 <div style={{gridColumn:"1 / span 2"}}>
570 <input id='discussion-create-title' placeholder='Title...'></input>
571 <input id='discussion-create-content' placeholder='Enter the comment...' ></input>
572 </div>
573 <div style={{placeItems:"end",gridColumn:"1 / span 2"}}>
574 <button id='discussion-create-button' onClick={()=>CreatePost()}>Post</button>
575 </div>
576
577 </div>
578
579 )):(
580 // Post screen
581 <div id='discussion-thread'>
582 <div>
583 <span>{discussionThread.title}</span>
584 <button onClick={()=>setDiscussionThread(null)}>X</button>
585 </div>
586
587 <div>
588 <img src={discussionThread.creator.avatar_link} alt="" />
589 <div>
590 <span>{discussionThread.creator.user_name}</span>
591 <span>{TimeAgo(new Date(discussionThread.created_at.replace("T"," ").replace("Z","")))}</span>
592 <span>{discussionThread.content}</span>
593 </div>
594 {discussionThread.comments!==null?
595 discussionThread.comments.sort((a, b) => new Date(a.date) - new Date(b.date))
596 .map(e=>(
597 <>
598 <img src={e.user.avatar_link} alt="" />
599 <div>
600 <span>{e.user.user_name}</span>
601 <span>{TimeAgo(new Date(e.date.replace("T"," ").replace("Z","")))}</span>
602 <span>{e.comment}</span>
603 </div>
604 </>
605
606 )):""}
607
608
609 </div>
610 <div id='discussion-send'>
611 <input type="text" placeholder={"Message"} onKeyDown={(e)=>e.key==="Enter"?PostComment():""}/>
612 <div><button onClick={()=>PostComment()}>Send</button></div>
613 </div>
614
615 </div>
616
617
618 )}
619
620 </section>
621
448 </main> 622 </main>
449 </> 623 </>
450 ) 624 )