Skip to content

Commit

Permalink
114 improving overall UI (#121)
Browse files Browse the repository at this point in the history
* rebase

* resolve conflit challengecard

* improve ui iter-2

* combine filter and fix stat

* fix filter, truncate description

* fix challenge bug

* finalize bubble, add active filter

* add gap between switch and showcomplete

* remove unnecessay style tag

* change bubble color final
  • Loading branch information
KhunKrit46 authored Dec 4, 2024
1 parent 8b32029 commit 6436193
Show file tree
Hide file tree
Showing 9 changed files with 1,618 additions and 1,201 deletions.
1,287 changes: 956 additions & 331 deletions client/custom.css

Large diffs are not rendered by default.

675 changes: 29 additions & 646 deletions client/package-lock.json

Large diffs are not rendered by default.

31 changes: 26 additions & 5 deletions client/src/components/ChallengeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,32 @@ function ChallengeCard({

const difficultyStars = useMemo(
() =>
Array.from({ length: difficulty + 1 }, (_, i) => <StarFill key={i} />),
[difficulty],
Array.from({ length: difficulty + 1 }, (_, i) => (
<StarFill
key={i}
style={{
color:
difficulty === 0
? "green"
: difficulty === 1
? "#DAA520"
: difficulty === 2
? "red"
: "black", // Default to black for other cases
}}
/>
)),
[difficulty]
);

const backgroundColor = completed ? "bg-success-subtle" : "";
const backgroundColor = completed
? "bg-success-subtle"
: "bg-secondary-subtle";

function handleDelete() {
if (
!window.confirm(
"Are you sure you want to delete the challenge '" + title + "'?",
"Are you sure you want to delete the challenge '" + title + "'?"
)
) {
return;
Expand Down Expand Up @@ -100,7 +116,12 @@ function ChallengeCard({
});

return (
<Card>
<Card
className="shadow"
style={{
border: "none",
}}
>
<Card.Header className={backgroundColor}>{difficultyStars}</Card.Header>
<Card.Body>
<Card.Title>{title}</Card.Title>
Expand Down
106 changes: 106 additions & 0 deletions client/src/components/FloatingStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from "react";
import { Badge } from "react-bootstrap";
import { FaTimes } from "react-icons/fa";

function FloatingStats({
totalEasy,
totalMedium,
totalHard,
completedEasy,
completedMedium,
completedHard,
}) {
const totalChallenges = totalEasy + totalMedium + totalHard;
const completedChallenges = completedEasy + completedMedium + completedHard;

return (
<div
style={{
position: "fixed",
bottom: "80px",
right: "20px",
width: "220px",
backgroundColor: "white",
borderRadius: "10px",
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
padding: "15px",
textAlign: "center",
zIndex: 1000,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h5 style={{ margin: 0 }}>Stats</h5>
<button
onClick={() => setVisible(false)}
style={{
background: "none",
border: "none",
cursor: "pointer",
fontSize: "14px",
}}
>
<FaTimes />
</button>
</div>

<div style={{ marginTop: "10px" }}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
fontSize: "24px",
fontWeight: "bold",
color: "black",
}}
>
{completedChallenges} / {totalChallenges}
<div style={{ fontSize: "14px", color: "gray" }}>
Overall Completed
</div>
</div>

<div style={{ marginTop: "15px" }}>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Badge bg="success">Easy</Badge>
<span>
{completedEasy} / {totalEasy}
</span>
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: "8px",
}}
>
<Badge style={{ backgroundColor: "#DAA520" }}>Medium</Badge>
<span>
{completedMedium} / {totalMedium}
</span>
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: "8px",
}}
>
<Badge bg="danger">Hard</Badge>
<span>
{completedHard} / {totalHard}
</span>
</div>
</div>
</div>
</div>
);
}

export default FloatingStats;
60 changes: 37 additions & 23 deletions client/src/pages/Challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { useEffect, useMemo, useState } from "react";
import { useParams } from "react-router-dom";
import { ChallengeDetails } from "../types/challengeDetails";
import { StarFill } from "react-bootstrap-icons";
import InstructionsPopup from '../components/InstructionsPopup';

import InstructionsPopup from "../components/InstructionsPopup";

const Challenge = () => {
const [details, setDetails] = useState<ChallengeDetails>();
Expand All @@ -15,8 +14,6 @@ const Challenge = () => {
const [showInstructions, setShowInstructions] = useState(false);
const [userRole, setUserRole] = useState<string | null>(null);



// Fetch user role
useEffect(() => {
fetch("/api/users/whoami")
Expand All @@ -30,16 +27,18 @@ const Challenge = () => {
setUserRole(data.role);
})
.catch((err: Error) => {
console.error("Failed fetching the user role. Error message: " + err.message);
console.error(
"Failed fetching the user role. Error message: " + err.message
);
});
}, []);

// Show instructions popup only once
useEffect(() => {
const instructionsShown = localStorage.getItem('instructionsShown');
const instructionsShown = localStorage.getItem("instructionsShown");
if (!instructionsShown) {
setShowInstructions(true);
localStorage.setItem('instructionsShown', 'true');
localStorage.setItem("instructionsShown", "true");
}
}, []);

Expand All @@ -57,24 +56,30 @@ const Challenge = () => {
setIsLoading(false);
})
.catch((err: Error) => {
console.error("Failed fetching the challenge number: " + id + "\nError message: " + err.message);
console.error(
"Failed fetching the challenge number: " +
id +
"\nError message: " +
err.message
);
});
}, [id]);

// Generate stars based on difficulty
const difficultyStars = useMemo(
() =>
details && details.difficulty >= 0 ? (
Array.from({ length: details.difficulty + 1 }, (_, i) => (
<StarFill key={i} className="text-warning" />
))
) : null,
[details],
details && details.difficulty >= 0
? Array.from({ length: details.difficulty + 1 }, (_, i) => (
<StarFill key={i} className="text-warning" />
))
: null,
[details]
);

if (isLoading) return <p>Loading...</p>;
if (details === undefined) return <p>Failed to load challenge details</p>;
if (details.hidden && userRole !== "admin") return <p>The challenge is hidden</p>;
if (details.hidden && userRole !== "admin")
return <p>The challenge is hidden</p>;

return (
<Container fluid>
Expand All @@ -86,7 +91,9 @@ const Challenge = () => {
<h2 className="text-dark fw-semibold fs-4">{details.outcome}</h2>
<div className="mt-2">{difficultyStars}</div>
{details.completed && (
<span className="mt-2 badge bg-success text-white">Completed</span>
<span className="mt-2 badge bg-success text-white">
Completed
</span>
)}
</header>
</Col>
Expand All @@ -99,11 +106,13 @@ const Challenge = () => {
<Col md={6}>
<h3 className="fs-5">Expected Functionality:</h3>
<ul>
{Object.entries(details.expectedFunctionality).map(([key, value]) => (
<li key={key}>
<strong>{key}:</strong> {value}
</li>
))}
{Object.entries(details.expectedFunctionality).map(
([key, value]) => (
<li key={key}>
<strong>{key}:</strong> {value}
</li>
)
)}
</ul>
</Col>
</Row>
Expand All @@ -124,13 +133,18 @@ const Challenge = () => {
<h3 className="fs-5 text-success">Key Design Patterns:</h3>
<ul>
{details.keyPatterns.map((pattern, index) => (
<li key={index}>{pattern + (pattern.slice(-1) !== "." ? "." : "")}</li>
<li key={index}>
{pattern + (pattern.slice(-1) !== "." ? "." : "")}
</li>
))}
</ul>
</Row>
)}
</section>
<InstructionsPopup show={showInstructions} handleClose={() => setShowInstructions(false)} />
<InstructionsPopup
show={showInstructions}
handleClose={() => setShowInstructions(false)}
/>
</Container>
);
};
Expand Down
Loading

0 comments on commit 6436193

Please sign in to comment.