Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
547648c
create hero section
JillStingray1 Jun 23, 2026
07c13d3
change image source to be imported directly
JillStingray1 Jun 25, 2026
497c4fa
create card component
JillStingray1 Jun 25, 2026
745a2ca
add news and event section to home page
JillStingray1 Jun 25, 2026
7c3e5ae
Merge branch 'main' of https://github.com/codersforcauses/electrify-e…
nicostellar Jun 27, 2026
4cfc8a4
Merge branch 'issue-2-Create_Home_Page' of github.com:codersforcauses…
JillStingray1 Jun 29, 2026
061c6cb
add mobile compatibility to stylesheet
JillStingray1 Jul 1, 2026
f2f256d
document components and relevant props
JillStingray1 Jul 1, 2026
ab3aa8e
fix formatting in css files
JillStingray1 Jul 1, 2026
b49b275
added card component
KKatariah Jun 27, 2026
d4eb042
wire CardContainer to render ContentCard
Chris-Jose Jul 2, 2026
995ab12
constrain card width and row max-width so arrows scroll instead of ev…
Chris-Jose Jul 2, 2026
196eebc
expand mock News/Events data and link cards to ContentCard fields
Chris-Jose Jul 2, 2026
462e7df
add scroll-snap so arrow clicks land on a full card instead of stoppi…
Chris-Jose Jul 2, 2026
6526e29
update hero CTA to 'Get Involved' to match Figma design
Chris-Jose Jul 2, 2026
1de8357
replace Recent News feed with WA Savings/News/Resources quick-link ti…
Chris-Jose Jul 2, 2026
190982f
update responsive view for mobile
JillStingray1 Jul 4, 2026
dabbe12
change css colors to use color variables
JillStingray1 Jul 4, 2026
b69a944
format code using prettier
JillStingray1 Jul 4, 2026
043bb36
created type for css modules
JillStingray1 Jul 11, 2026
8103668
Merge branch 'main' into issue-2-Create_Home_Page
KKatariah Jul 11, 2026
2df44cc
adjusted style on homepage
JillStingray1 Jul 11, 2026
0a10438
fix formatting in css module
JillStingray1 Jul 11, 2026
5d29a99
add image module in declarations
JillStingray1 Jul 11, 2026
4e0bc0e
change image declaration to add src
JillStingray1 Jul 11, 2026
49e993b
change color on hover for hero button
JillStingray1 Jul 17, 2026
28ff00b
add changing of number of available cards based on viewport width
JillStingray1 Jul 18, 2026
0a30b9d
fix button misalignment on hover
JillStingray1 Jul 18, 2026
851b5eb
Merge branch 'main' into issue-2-Create_Home_Page
JillStingray1 Jul 18, 2026
815240b
remove redundant include declarations
JillStingray1 Jul 18, 2026
ace235d
fix: made home page reflect Montserrat font
Jason-CJS Jul 18, 2026
050691d
Change JSX setting to react-jsx in tsconfig to match with main
Jason-CJS Jul 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions client/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module "*.module.css" {
const classes: { [key: string]: string };
export default classes;
}

declare module "*.png" {
const content: {
src: string;
height: number;
width: number;
blurDataURL?: string;
};
export default content;
}
32 changes: 32 additions & 0 deletions client/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Image, { StaticImageData } from "next/image";

import styles from "@/styles/components/card.module.css";

/**
* Interface that contains fields for a card.
*/
export interface CardProp {
image: StaticImageData;
alt: string;
title: string;
body: string;
}

/** A Placeholder Card component for the front page.
*
* @param param0 CardProp which contain fields for the card
* @returns
*/
export default function Card({ image, alt, title, body }: CardProp) {
return (
<div className={styles["card"]}>
<div className={styles["card-img"]}>
<Image src={image} alt={alt} />
</div>
<div className={styles["card-body"]}>
<h1 className="text-4xl font-bold text-gray-900">{title}</h1>
<p>{body}</p>
</div>
</div>
);
}
48 changes: 48 additions & 0 deletions client/src/components/ui/card_container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useRef } from "react";

import styles from "@/styles/components/card_container.module.css";

import { ContentCard, ContentCardProps } from "../content-card";

/** Horizontally scrolling container for cards, On widescreen resolutions, scrolls with side buttons
* mobile scrolling is done with pointer.
*
* @param param0 An Array of ContentCardProps which represents the cards to be displayed
* @returns
*/
export default function CardContainer({
cards,
}: {
cards: Array<ContentCardProps>;
}) {
const containerRef = useRef<HTMLDivElement>(null);

const scroll = (direction: "left" | "right") => {
if (containerRef.current) {
containerRef.current.scrollBy({
left: direction === "right" ? 400 : -400,
behavior: "smooth",
});
}
};

return (
<div className={styles["wrapper"]}>
<button className={styles["arrow"]} onClick={() => scroll("left")}>
&#8592;
</button>
<div className={styles["card-container"]} ref={containerRef}>
{cards.map((card_prop) => (
<ContentCard
key={card_prop.href}
{...card_prop}
className="w-[clamp(18rem,30vw,24rem)] shrink-0 snap-start"
/>
))}
</div>
<button className={styles["arrow"]} onClick={() => scroll("right")}>
&#8594;
</button>
</div>
);
}
2 changes: 1 addition & 1 deletion client/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Navbar from "@/components/ui/Navbar";

const montserrat = Montserrat({
subsets: ["latin"],
variable: "--font-sans",
variable: "--font-montserrat",
weight: ["400", "500", "600", "700", "800"],
});

Expand Down
120 changes: 101 additions & 19 deletions client/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,118 @@
import { Inter as FontSans } from "next/font/google";
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";

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

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

const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
});
/**
* Returns a list of upcoming events as ContentCardProps to display
*
* @returns List of events sorted in order
*/
function getEvents(): Array<ContentCardProps> {
// Currently a mock function, doesn't do anything other than make some stuff to display
// TODO: replace with a real fetch against the Events endpoint once #3 is closed
const mockEvents = [
{ title: "Community Solar Info Night", dateTime: "12 Jul 2026" },
{ title: "Electrify Your Home Workshop", dateTime: "19 Jul 2026" },
{ title: "EV Test Drive Day", dateTime: "26 Jul 2026" },
{ title: "Heat Pump Q&A Session", dateTime: "2 Aug 2026" },
{ title: "Battery Storage Info Session", dateTime: "9 Aug 2026" },
];

return mockEvents.map((event, index) => ({
imageSrc: hero_image.src,
imageAlt: event.title,
title: event.title,
description:
"Join us to learn how to electrify your home and cut your energy bills.",
dateTime: event.dateTime,
href: `/events/${index + 1}`,
}));
}

/**
* Returns the three quick-link tiles (WA Savings, News, Resources) shown
* below the hero, linking out to their respective pages.
*
* @returns List of quick-link tiles
*/
function getQuickLinks(): Array<ContentCardProps> {
return [
{
imageSrc: hero_image.src,
imageAlt: "WA Savings",
title: "WA Savings",
description:
"See how much you could save by switching to electric appliances and solar in WA.",
href: "/wa-savings",
},
{
imageSrc: hero_image.src,
imageAlt: "News",
title: "News",
description:
"Read the latest updates from Electrify Everything WA and our community.",
href: "/news",
},
{
imageSrc: hero_image.src,
imageAlt: "Resources",
title: "Resources",
description:
"Explore guides and resources to help you electrify your home.",
href: "/resources",
},
];
}

export default function Home() {
const [clicked, setClicked] = useState(false);
const { data, isLoading } = usePings({
enabled: clicked,
});
const router = useRouter();
const events = getEvents();
const quickLinks = getQuickLinks();

return (
<main
className={cn(
"flex min-h-screen flex-col items-center gap-4 p-24 font-sans",
fontSans.variable,
)}
>
<h1 className="text-3xl text-primary">Test title</h1>
<Button onClick={() => setClicked(true)}>
{isLoading ? "Loading" : "Ping"}
</Button>
<p>
Response from server: <span>{data as string}</span>
</p>
<div className={styles["hero"]}>
<div className={styles["hero-body"]}>
<div className={styles["hero-text"]}>
Electrifying our households to build a safer, more sustainable
future.
</div>
<Button
className={styles["hero-button"]}
onClick={() => router.push("/get-involved")}
>
Get Involved <span className={styles["arrow"]}>▸</span>
</Button>
</div>
<div className={styles["hero-img"]}>
<Image
src={hero_image}
alt={
"Image of a house with electric alternative to fossil fuel products"
}
/>
</div>
</div>
<div className={styles["news-and-events"]}>
<div className="flex justify-center">
<CardContainer cards={quickLinks} />
</div>
<h1 className="m-5 text-5xl font-bold">Upcoming Events</h1>
<div className="flex justify-center">
<CardContainer cards={events} />
</div>
</div>
</main>
);
}
Binary file added client/src/public/hero_img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions client/src/styles/components/card.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.card {
width: 32%;
aspect-ratio: 1 / 1;
margin: 0.5%;
outline: 2px solid black;
border-radius: 10px;
background-color: white;
filter: drop-shadow(4px 5px 4px lightgrey);
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.card-img {
height: 40%;
width: auto;
align-items: center;
}
.card-body {
height: 60%;
padding: 1rem;
}

@media (max-width: 768px) {
.card {
width: 80%;
aspect-ratio: 4 / 5;
margin: 0 2%;
}

.card-body h1 {
font-size: 1.25rem !important;
}

.card-body p {
font-size: 0.9rem;
}
}
72 changes: 72 additions & 0 deletions client/src/styles/components/card_container.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
}

.card-container {
display: flex;
overflow-x: auto;
gap: 1rem;
padding: 0rem;
/* Cap the row so roughly 3 full-width cards show at once, with a peek of the
next one hinting there's more to scroll to -- without this, a wide enough
screen can fit every card and the arrows do nothing. */
max-width: 74rem;
scroll-behavior: smooth;
/* Snap to the start of each card so a scroll click never leaves a card
half-visible -- the browser handles alignment natively instead of us
guessing a pixel amount in JS that would drift out of sync with the
card's responsive width. */
scroll-snap-type: x mandatory;
/* hide scrollbar */
scrollbar-width: none;
}

.card-container::-webkit-scrollbar {
display: none;
}

.arrow {
background: hsl(var(--background));
border: 1px solid hsl(var(--border));
border-radius: 999px;
width: 2.5rem;
height: 2.5rem;
font-size: 1.25rem;
cursor: pointer;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

.arrow:hover {
background: hsl(var(--accent));
}

@media (min-width: 52rem) and (max-width: 82rem) {
.arrow {
display: none;
}

.wrapper {
gap: 0;
}

.card-container {
max-width: calc(2 * clamp(18rem, 30vw, 24rem) + 1rem);
}
}

@media (max-width: 51rem) {
.arrow {
display: none;
}

.wrapper {
gap: 0;
}

.card-container {
max-width: calc(1 * clamp(18rem, 30vw, 24rem) + 1rem);
}
}
Loading
Loading