Skip to content

Commit

Permalink
Homepage (#52)
Browse files Browse the repository at this point in the history
* fixed the profile page and added user data to solution card

* added recent solutions and created at solution timestamp

* finished updating the homepage

* minor comment cleanup
  • Loading branch information
Vladmidir authored Apr 3, 2024
1 parent cd0b26c commit 86c25b2
Show file tree
Hide file tree
Showing 16 changed files with 330 additions and 151 deletions.
2 changes: 1 addition & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const router = createBrowserRouter([
element: <Challenges />
},
{
path: "profile/",
path: "profile/:username",
element: <Profile />
},
{
Expand Down
22 changes: 21 additions & 1 deletion client/src/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,33 @@ import { Container, Nav, Navbar } from "react-bootstrap";
import { useLocation, useNavigate } from "react-router-dom";
import { PersonCircle } from "react-bootstrap-icons";
import { NAV_CONFIG } from "../App.tsx";
import { useEffect, useState } from "react";

function NavigationBar() {
const location = useLocation().pathname;
const [username, setUsername] = useState<string | null>(null);
const navigate = useNavigate();

//fetch the username from the server
useEffect(() => {
fetch("/api/users/whoami")
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<{username: string}>;
})
.then((data) => {
console.log("Fetched username: " + data.username);
setUsername(data.username);
})
.catch((err: Error) => { // Add the error type 'Error'
console.error("Failed fetching the username\nError message: " + err.message);
});
}, [username]);

const handleProfileClick = () => {
navigate(NAV_CONFIG.profile.href);
navigate(NAV_CONFIG.profile.href + "/" + username);
};

return (
Expand Down
80 changes: 72 additions & 8 deletions client/src/components/SolutionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,84 @@ import Button from "./Button.tsx";
type SolutionCardProps = {
imgSrc: string;
title: string;
href: string;
id: string;
description: string;
author: string;
createdAt: string;
};

function SolutionCard({ imgSrc, title, href, description }: SolutionCardProps) {

function timeAgo(dateString: string) {
const date = new Date(dateString);
const now = new Date();

let diff = Math.abs(now.getTime() - date.getTime()); // difference in milliseconds

const years = Math.floor(diff / (1000 * 60 * 60 * 24 * 365));
diff -= years * (1000 * 60 * 60 * 24 * 365);

const months = Math.floor(diff / (1000 * 60 * 60 * 24 * 30));
diff -= months * (1000 * 60 * 60 * 24 * 30);

const days = Math.floor(diff / (1000 * 60 * 60 * 24));
diff -= days * (1000 * 60 * 60 * 24);

const hours = Math.floor(diff / (1000 * 60 * 60));
diff -= hours * (1000 * 60 * 60);

const minutes = Math.floor(diff / (1000 * 60)) || 1; // Default to at least 1 minute

let result = '';
if (years === 1) {
result += `${years} year `;
} else if (years > 1) {
result += `${years} years `;
} else if (months === 1) {
result += `${months} month `;
} else if (months > 1) {
result += `${months} months `;
} else if (days === 1) {
result += `${days} day `;
} else if (days > 1) {
result += `${days} days `;
} else if (hours === 1) {
result += `${hours} hour `;
} else if (hours > 1) {
result += `${hours} hours `;
} else if (minutes === 1) {
result += `${minutes} minute `;
} else {
result += `${minutes} minutes `;
}
result += "ago";


return result.trim();
}


function SolutionCard({ imgSrc, title, id, description, author, createdAt }: SolutionCardProps) {
return (
<Card>
<Card.Img variant={"top"} src={imgSrc} />
<Card.Body>
<Card.Header>
<Card.Title>{title}</Card.Title>
<Card.Text>{description}</Card.Text>
<Button variant="primary" href={href}>
Review
</Button>
<Card.Subtitle className="mb-2 text-muted">
<Card.Link href={`/profile/${author}`}>
{author}
</Card.Link>
</Card.Subtitle>
</Card.Header>
<Card.Body>
<Card.Img variant={"top"} src={`/api/solutions/diagrams/${imgSrc}`} />
<Card.Text className="my-2">{description}</Card.Text>
<div className="d-flex justify-content-between">
<Button variant="primary" href={`/solution/${id}`}>
Review
</Button>
<Card.Text className="text-muted mt-2">
Posted {timeAgo(createdAt)}
</Card.Text>
</div>
</Card.Body>
</Card>
);
Expand Down
146 changes: 85 additions & 61 deletions client/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,97 @@
import { Col, Container, Row, Stack } from "react-bootstrap";
import { useState, useEffect } from "react";
import Button from "../components/Button.tsx";
import SolutionCard, {
SolutionCardProps,
} from "../components/SolutionCard.tsx";
import SolutionCard from "../components/SolutionCard.tsx";
import { ArrowUpRightSquare } from "react-bootstrap-icons";
import ChallengeCard from "../components/ChallengeCard.tsx";
import { ChallengeDifficulties } from "../types/challengeDifficulties.ts";
import { SolutionData } from "../types/SolutionData.ts";
import { ChallengeDetailsShort } from "../types/ChallengeDetailsShort.ts";

const DEMO_SOLUTION_CARDS: SolutionCardProps[] = Array(5).fill({
title: "Example Challenge 1: Airport Management System",
description:
"Some quick example text to build on the card title and make up the bulk of the card's content.",
imgSrc:
"https://images.unsplash.com/photo-1596496181871-9681eacf9764?q=80&w=2086&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
href: "/home",
});

const DEMO_CHALLENGE_CARDS: ChallengeDetailsShort[] = Array(3).fill({
title: "Demo Challenge Card",
generalDescription:
"In this demo challenge you will be demoing our platform. From creating UML diagrams to submitting" +
" and getting review, we provide you all you need to become a Software Architecture monster!",
id: 0,
difficulty: ChallengeDifficulties.MEDIUM,
});

function Home() {
// TODO: fetch the solutions
return (
<section>
{/* Recent Solutions */}
<Container className={"mt-5"}>
<h2 className={"mb-3"}>Recent Solutions</h2>
<Row sm={1} lg={3} className={"gx-4 gy-4"}>
{DEMO_SOLUTION_CARDS.map((solutionCardProps) => (
<Col key={solutionCardProps.href}>
<SolutionCard {...solutionCardProps} />
</Col>
))}
</Row>
{/* See more solutions button */}
<Button variant={"outline-primary"} className={"mt-3"} href={"/home"}>
See More <ArrowUpRightSquare style={{ marginLeft: "0.5rem" }} />
</Button>
</Container>
const [solutions, setSolutions] = useState<SolutionData[]>([]);
const [challenges, setChallenges] = useState<ChallengeDetailsShort[]>([]);

//fetch the recent solutions
useEffect(() => {
fetch("/api/solutions/recent/3")
.then((resp) => resp.json() as Promise<SolutionData[]>)
.then((data: SolutionData[]) => {
setSolutions(data);
})
.catch((err) => {
console.error(err);
});
}, []);

//fetch the suggested challenges
useEffect(() => {
fetch("/api/challenges/suggested")
.then((resp) => resp.json() as Promise<ChallengeDetailsShort[]>)
.then((data: ChallengeDetailsShort[]) => {
setChallenges(data);
})
.catch((err) => {
console.error(err);
});
}, []);

return (
<section>
{/* Recent Solutions */}
<Container className={"mt-5"}>
<h2 className={"mb-3"}>Recent Solutions</h2>
<Row sm={1} lg={3} className={"gx-4 gy-4"}>
{solutions.map((solution: SolutionData) => {
return (
<Col key={solution.title}>
<SolutionCard
imgSrc={solution.diagram}
title={solution.title}
id={solution.id.toString()}
description={solution.description}
author={solution.User.username}
createdAt={solution.createdAt}
/>
</Col>
);
})}
</Row>
{/* See more solutions button */}
<Button variant={"outline-primary"} className={"mt-3"} href={"/solutions"}>
See More <ArrowUpRightSquare style={{ marginLeft: "0.5rem" }} />
</Button>
</Container>

{/* Suggested Challenges */}
<Container className={"mt-5"}>
<Stack
direction={"horizontal"}
className={"justify-content-between mb-3"}
>
<h2>Suggested Challenges</h2>
{/* See more challenges button */}
<Button variant={"outline-primary"} href={"/home"}>
See More <ArrowUpRightSquare style={{ marginLeft: "0.5rem" }} />
</Button>
</Stack>
<Row sm={1} lg={3} className={"gx-4 gy-4"}>
{DEMO_CHALLENGE_CARDS.map((challengeCardProps) => (
<Col key={challengeCardProps.id}>
<ChallengeCard {...challengeCardProps} />
</Col>
))}
</Row>
</Container>
</section>
);
{/* Suggested Challenges */}
<Container className={"my-5"}>
<Stack
direction={"horizontal"}
className={"justify-content-between mb-3"}
>
<h2>Suggested Challenges</h2>
{/* See more challenges button */}
<Button variant={"outline-primary"} href={"/challenges"}>
See More <ArrowUpRightSquare style={{ marginLeft: "0.5rem" }} />
</Button>
</Stack>
<Row sm={1} lg={3} className={"gx-4 gy-4"}>
{challenges.map((challenge: ChallengeDetailsShort) => {
return (
<Col key={challenge.id}>
<ChallengeCard
title={challenge.title}
difficulty={challenge.difficulty}
generalDescription={challenge.generalDescription}
id={challenge.id}
/>
</Col>
);
})}
</Row>
</Container>
</section>
);
}

export default Home;
7 changes: 0 additions & 7 deletions client/src/pages/Profile.css

This file was deleted.

Loading

0 comments on commit 86c25b2

Please sign in to comment.