Skip to content

Commit 5535580

Browse files
authored
Merge pull request #21 from codersforcauses/issue-2-Create_Home_Page
Issue 2 create home page
2 parents 39ecdeb + 050691d commit 5535580

9 files changed

Lines changed: 436 additions & 20 deletions

File tree

client/declarations.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
declare module "*.module.css" {
2+
const classes: { [key: string]: string };
3+
export default classes;
4+
}
5+
6+
declare module "*.png" {
7+
const content: {
8+
src: string;
9+
height: number;
10+
width: number;
11+
blurDataURL?: string;
12+
};
13+
export default content;
14+
}

client/src/components/ui/card.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Image, { StaticImageData } from "next/image";
2+
3+
import styles from "@/styles/components/card.module.css";
4+
5+
/**
6+
* Interface that contains fields for a card.
7+
*/
8+
export interface CardProp {
9+
image: StaticImageData;
10+
alt: string;
11+
title: string;
12+
body: string;
13+
}
14+
15+
/** A Placeholder Card component for the front page.
16+
*
17+
* @param param0 CardProp which contain fields for the card
18+
* @returns
19+
*/
20+
export default function Card({ image, alt, title, body }: CardProp) {
21+
return (
22+
<div className={styles["card"]}>
23+
<div className={styles["card-img"]}>
24+
<Image src={image} alt={alt} />
25+
</div>
26+
<div className={styles["card-body"]}>
27+
<h1 className="text-4xl font-bold text-gray-900">{title}</h1>
28+
<p>{body}</p>
29+
</div>
30+
</div>
31+
);
32+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useRef } from "react";
2+
3+
import styles from "@/styles/components/card_container.module.css";
4+
5+
import { ContentCard, ContentCardProps } from "../content-card";
6+
7+
/** Horizontally scrolling container for cards, On widescreen resolutions, scrolls with side buttons
8+
* mobile scrolling is done with pointer.
9+
*
10+
* @param param0 An Array of ContentCardProps which represents the cards to be displayed
11+
* @returns
12+
*/
13+
export default function CardContainer({
14+
cards,
15+
}: {
16+
cards: Array<ContentCardProps>;
17+
}) {
18+
const containerRef = useRef<HTMLDivElement>(null);
19+
20+
const scroll = (direction: "left" | "right") => {
21+
if (containerRef.current) {
22+
containerRef.current.scrollBy({
23+
left: direction === "right" ? 400 : -400,
24+
behavior: "smooth",
25+
});
26+
}
27+
};
28+
29+
return (
30+
<div className={styles["wrapper"]}>
31+
<button className={styles["arrow"]} onClick={() => scroll("left")}>
32+
&#8592;
33+
</button>
34+
<div className={styles["card-container"]} ref={containerRef}>
35+
{cards.map((card_prop) => (
36+
<ContentCard
37+
key={card_prop.href}
38+
{...card_prop}
39+
className="w-[clamp(18rem,30vw,24rem)] shrink-0 snap-start"
40+
/>
41+
))}
42+
</div>
43+
<button className={styles["arrow"]} onClick={() => scroll("right")}>
44+
&#8594;
45+
</button>
46+
</div>
47+
);
48+
}

client/src/pages/_app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Navbar from "@/components/ui/Navbar";
99

1010
const montserrat = Montserrat({
1111
subsets: ["latin"],
12-
variable: "--font-sans",
12+
variable: "--font-montserrat",
1313
weight: ["400", "500", "600", "700", "800"],
1414
});
1515

client/src/pages/index.tsx

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,118 @@
1-
import { Inter as FontSans } from "next/font/google";
2-
import { useState } from "react";
1+
import Image from "next/image";
2+
import { useRouter } from "next/navigation";
33

4-
import { usePings } from "@/hooks/pings";
4+
import { ContentCard, ContentCardProps } from "@/components/content-card";
5+
import CardContainer from "@/components/ui/card_container";
56
import { cn } from "@/lib/utils";
7+
import hero_image from "@/public/hero_img.png";
8+
import styles from "@/styles/index.module.css";
69

710
import { Button } from "../components/ui/button";
811

9-
const fontSans = FontSans({
10-
subsets: ["latin"],
11-
variable: "--font-sans",
12-
});
12+
/**
13+
* Returns a list of upcoming events as ContentCardProps to display
14+
*
15+
* @returns List of events sorted in order
16+
*/
17+
function getEvents(): Array<ContentCardProps> {
18+
// Currently a mock function, doesn't do anything other than make some stuff to display
19+
// TODO: replace with a real fetch against the Events endpoint once #3 is closed
20+
const mockEvents = [
21+
{ title: "Community Solar Info Night", dateTime: "12 Jul 2026" },
22+
{ title: "Electrify Your Home Workshop", dateTime: "19 Jul 2026" },
23+
{ title: "EV Test Drive Day", dateTime: "26 Jul 2026" },
24+
{ title: "Heat Pump Q&A Session", dateTime: "2 Aug 2026" },
25+
{ title: "Battery Storage Info Session", dateTime: "9 Aug 2026" },
26+
];
27+
28+
return mockEvents.map((event, index) => ({
29+
imageSrc: hero_image.src,
30+
imageAlt: event.title,
31+
title: event.title,
32+
description:
33+
"Join us to learn how to electrify your home and cut your energy bills.",
34+
dateTime: event.dateTime,
35+
href: `/events/${index + 1}`,
36+
}));
37+
}
38+
39+
/**
40+
* Returns the three quick-link tiles (WA Savings, News, Resources) shown
41+
* below the hero, linking out to their respective pages.
42+
*
43+
* @returns List of quick-link tiles
44+
*/
45+
function getQuickLinks(): Array<ContentCardProps> {
46+
return [
47+
{
48+
imageSrc: hero_image.src,
49+
imageAlt: "WA Savings",
50+
title: "WA Savings",
51+
description:
52+
"See how much you could save by switching to electric appliances and solar in WA.",
53+
href: "/wa-savings",
54+
},
55+
{
56+
imageSrc: hero_image.src,
57+
imageAlt: "News",
58+
title: "News",
59+
description:
60+
"Read the latest updates from Electrify Everything WA and our community.",
61+
href: "/news",
62+
},
63+
{
64+
imageSrc: hero_image.src,
65+
imageAlt: "Resources",
66+
title: "Resources",
67+
description:
68+
"Explore guides and resources to help you electrify your home.",
69+
href: "/resources",
70+
},
71+
];
72+
}
1373

1474
export default function Home() {
15-
const [clicked, setClicked] = useState(false);
16-
const { data, isLoading } = usePings({
17-
enabled: clicked,
18-
});
75+
const router = useRouter();
76+
const events = getEvents();
77+
const quickLinks = getQuickLinks();
1978

2079
return (
2180
<main
2281
className={cn(
2382
"flex min-h-screen flex-col items-center gap-4 p-24 font-sans",
24-
fontSans.variable,
2583
)}
2684
>
27-
<h1 className="text-3xl text-primary">Test title</h1>
28-
<Button onClick={() => setClicked(true)}>
29-
{isLoading ? "Loading" : "Ping"}
30-
</Button>
31-
<p>
32-
Response from server: <span>{data as string}</span>
33-
</p>
85+
<div className={styles["hero"]}>
86+
<div className={styles["hero-body"]}>
87+
<div className={styles["hero-text"]}>
88+
Electrifying our households to build a safer, more sustainable
89+
future.
90+
</div>
91+
<Button
92+
className={styles["hero-button"]}
93+
onClick={() => router.push("/get-involved")}
94+
>
95+
Get Involved <span className={styles["arrow"]}></span>
96+
</Button>
97+
</div>
98+
<div className={styles["hero-img"]}>
99+
<Image
100+
src={hero_image}
101+
alt={
102+
"Image of a house with electric alternative to fossil fuel products"
103+
}
104+
/>
105+
</div>
106+
</div>
107+
<div className={styles["news-and-events"]}>
108+
<div className="flex justify-center">
109+
<CardContainer cards={quickLinks} />
110+
</div>
111+
<h1 className="m-5 text-5xl font-bold">Upcoming Events</h1>
112+
<div className="flex justify-center">
113+
<CardContainer cards={events} />
114+
</div>
115+
</div>
34116
</main>
35117
);
36118
}

client/src/public/hero_img.png

214 KB
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.card {
2+
width: 32%;
3+
aspect-ratio: 1 / 1;
4+
margin: 0.5%;
5+
outline: 2px solid black;
6+
border-radius: 10px;
7+
background-color: white;
8+
filter: drop-shadow(4px 5px 4px lightgrey);
9+
display: flex;
10+
flex-direction: column;
11+
flex-shrink: 0;
12+
}
13+
.card-img {
14+
height: 40%;
15+
width: auto;
16+
align-items: center;
17+
}
18+
.card-body {
19+
height: 60%;
20+
padding: 1rem;
21+
}
22+
23+
@media (max-width: 768px) {
24+
.card {
25+
width: 80%;
26+
aspect-ratio: 4 / 5;
27+
margin: 0 2%;
28+
}
29+
30+
.card-body h1 {
31+
font-size: 1.25rem !important;
32+
}
33+
34+
.card-body p {
35+
font-size: 0.9rem;
36+
}
37+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
.wrapper {
2+
display: flex;
3+
align-items: center;
4+
gap: 0.5rem;
5+
}
6+
7+
.card-container {
8+
display: flex;
9+
overflow-x: auto;
10+
gap: 1rem;
11+
padding: 0rem;
12+
/* Cap the row so roughly 3 full-width cards show at once, with a peek of the
13+
next one hinting there's more to scroll to -- without this, a wide enough
14+
screen can fit every card and the arrows do nothing. */
15+
max-width: 74rem;
16+
scroll-behavior: smooth;
17+
/* Snap to the start of each card so a scroll click never leaves a card
18+
half-visible -- the browser handles alignment natively instead of us
19+
guessing a pixel amount in JS that would drift out of sync with the
20+
card's responsive width. */
21+
scroll-snap-type: x mandatory;
22+
/* hide scrollbar */
23+
scrollbar-width: none;
24+
}
25+
26+
.card-container::-webkit-scrollbar {
27+
display: none;
28+
}
29+
30+
.arrow {
31+
background: hsl(var(--background));
32+
border: 1px solid hsl(var(--border));
33+
border-radius: 999px;
34+
width: 2.5rem;
35+
height: 2.5rem;
36+
font-size: 1.25rem;
37+
cursor: pointer;
38+
flex-shrink: 0;
39+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
40+
}
41+
42+
.arrow:hover {
43+
background: hsl(var(--accent));
44+
}
45+
46+
@media (min-width: 52rem) and (max-width: 82rem) {
47+
.arrow {
48+
display: none;
49+
}
50+
51+
.wrapper {
52+
gap: 0;
53+
}
54+
55+
.card-container {
56+
max-width: calc(2 * clamp(18rem, 30vw, 24rem) + 1rem);
57+
}
58+
}
59+
60+
@media (max-width: 51rem) {
61+
.arrow {
62+
display: none;
63+
}
64+
65+
.wrapper {
66+
gap: 0;
67+
}
68+
69+
.card-container {
70+
max-width: calc(1 * clamp(18rem, 30vw, 24rem) + 1rem);
71+
}
72+
}

0 commit comments

Comments
 (0)