diff --git a/client/src/components/content-card.tsx b/client/src/components/content-card.tsx index 30ff462..68302da 100644 --- a/client/src/components/content-card.tsx +++ b/client/src/components/content-card.tsx @@ -116,7 +116,7 @@ export function ContentCard({
{buttonLabel} diff --git a/client/src/components/resources-horizontal-card.tsx b/client/src/components/resources-horizontal-card.tsx new file mode 100644 index 0000000..602fd5f --- /dev/null +++ b/client/src/components/resources-horizontal-card.tsx @@ -0,0 +1,87 @@ +import { ArrowRight, ImageOff } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; + +import { cn } from "@/lib/utils"; + +export interface HorizontalCardProps { + className?: string; + imageSrc: string; + imageAlt: string; + title: string; + description: string; + href: string; + buttonLabel?: string; + author?: string; + dateTime?: string; +} + +export function HorizontalCard({ + className, + imageSrc, + imageAlt, + title, + description, + href, + buttonLabel = "View More", + author, + dateTime, +}: HorizontalCardProps) { + const meta = [author, dateTime].filter(Boolean).join(" • "); + + return ( +
+
+ {imageSrc ? ( + {imageAlt} + ) : ( +
+
+ )} +
+ +
+ {meta ? ( +

+ {meta} +

+ ) : null} + +

+ {title} +

+ +

+ {description} +

+ + + {buttonLabel} +
+
+ ); +} diff --git a/client/src/components/savings-card.tsx b/client/src/components/savings-card.tsx new file mode 100644 index 0000000..415f39b --- /dev/null +++ b/client/src/components/savings-card.tsx @@ -0,0 +1,106 @@ +import styles from "@/styles/components/savings_card.module.css"; + +export interface SavingCardProp { + heading: string; + body: string; + savings: string; + data: Record>; +} + +// Your theme only defines primary/secondary/muted/accent/destructive — no green or +// purple token, so those two fall back to plain Tailwind colors. Swap in your own +// tokens here if you add them later. +const ROW_DOT_CLASSES = [ + "bg-emerald-500", // no theme equivalent + "bg-destructive", + "bg-foreground", + "bg-violet-500", // no theme equivalent +]; + +export default function SavingHorizontalCard({ + heading, + body, + savings, + data, +}: SavingCardProp) { + const columns = Object.keys(data); + // row labels come from the inner keys — assumes every column has the same set of rows + const rows = columns.length > 0 ? Object.keys(data[columns[0]]) : []; + + const columnTotal = (col: string) => + rows.reduce((sum, row) => sum + (data[col][row] ?? 0), 0); + + return ( +
+ {/* left panel */} +
+
+

+ {heading} +

+

+ {body} +

+
+ {/* no "warning"/amber token in the theme, so this uses plain amber rather than a css var */} +
+

You could save

+

+ ${savings} +

+

over 15 years

+
+
+ + {/* right panel — table replaces the bar chart */} +
+ + + + + {columns.map((col) => ( + + ))} + + + + {rows.map((row, i) => ( + + + {columns.map((col) => ( + + ))} + + ))} + +
+
+ {col} +
+
+ ${columnTotal(col).toLocaleString()} +
+
+ + {row} + + ${data[col][row].toLocaleString()} +
+
+
+ ); +} diff --git a/client/src/hooks/resources.ts b/client/src/hooks/resources.ts new file mode 100644 index 0000000..9b71ea4 --- /dev/null +++ b/client/src/hooks/resources.ts @@ -0,0 +1,57 @@ +import { useQuery } from "@tanstack/react-query"; + +export interface Resource { + id: string; + date_made: string; + author: string; + image: string | null; + name: string; + slug: string; + summary: string; + type: "page" | "file"; + body: string; + file_url: string; + file_name: string; +} + +const MOCK_RESOURCES: Resource[] = [ + { + id: "1", + date_made: "2022-02-04", + author: "John Doe", + image: null, + name: "New feature available on Devias", + slug: "new-feature", + summary: + "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.", + type: "page", + body: "", + file_url: "", + file_name: "", + }, + { + id: "2", + date_made: "2023-08-15", + author: "Jane Smith", + image: null, + name: "How to electrify your home", + slug: "electrify-your-home", + summary: "A practical guide to going all-electric in Western Australia.", + type: "page", + body: "", + file_url: "", + file_name: "", + }, +]; + +export function useResources() { + return useQuery({ + queryKey: ["resources"], + queryFn: async (): Promise => { + // Swap this block for the real fetch once the endpoint lands: + // const res = await fetch("/api/resources/"); + // return res.json(); + return MOCK_RESOURCES; + }, + }); +} diff --git a/client/src/styles/components/savings_card.module.css b/client/src/styles/components/savings_card.module.css new file mode 100644 index 0000000..fdbd381 --- /dev/null +++ b/client/src/styles/components/savings_card.module.css @@ -0,0 +1,53 @@ +Savinghorizontalcard.module · CSS .card { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +} + +.leftPanel { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.rightPanel { + flex: 1; + overflow-x: auto; +} + +.table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; +} + +.rowLabel { + display: flex; + align-items: center; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 9999px; + display: inline-block; + flex-shrink: 0; +} + +@media (min-width: 640px) { + .card { + flex-direction: row; + gap: 1.5rem; + } + + .rightPanel { + flex: 1.2; + } + + .dot { + width: 10px; + height: 10px; + } +}