Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Node environment (development, production, test)
NODE_ENV=development
# For Next.js apps, we set this in production environments
# `next dev` and `next build` will set this automatically in your development environment
# NODE_ENV=development

# NextAuth Configuration
# Required for authentication
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm run type-check
- run: pnpm exec biome check --error-on-warnings
- name: Build
run: pnpm run build
env:
# Dummy values for build-time env vars (not needed at build time, only runtime)
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: build-time-secret
GOOGLE_CLIENT_ID: build-time-client-id
GOOGLE_CLIENT_SECRET: build-time-client-secret
9 changes: 5 additions & 4 deletions src/app/[id]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@ async function loadGoogleFont(font: string, weight: number, text: string) {
export default async function OpengraphImage({
params,
}: {
params: { id: string };
params: Promise<{ id: string }>;
}) {
const bill = await getUnifiedBillById(params.id);
const { id } = await params;
const bill = await getUnifiedBillById(id);
const status = (bill?.final_judgment || "abstain").toLowerCase();
const voteText =
status === "yes"
? "Vote: Yes"
: status === "no"
? "Vote: No"
: "Vote: Abstain";
const textForFont = `${bill?.short_title || bill?.title || params.id} ${voteText} ${PROJECT_NAME} Build Canada Policy Tracker Powered by The Civics Project`;
const textForFont = `${bill?.short_title || bill?.title || id} ${voteText} ${PROJECT_NAME} Build Canada Policy Tracker Powered by The Civics Project`;
let interRegular: ArrayBuffer | undefined;
let interBold: ArrayBuffer | undefined;
try {
Expand All @@ -69,7 +70,7 @@ export default async function OpengraphImage({
final_judgment: bill?.final_judgment,
rationale: bill?.rationale,
genres: bill?.genres,
fallbackId: params.id,
fallbackId: id,
}}
/>,
{
Expand Down
12 changes: 8 additions & 4 deletions src/app/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ import { buildAbsoluteUrl, buildRelativePath } from "@/utils/basePath";
import {
BUILD_CANADA_TWITTER_HANDLE,
BUILD_CANADA_URL,
PAGE_REVALIDATE_INTERVAL,
} from "@/consts/general";
import { BillShare } from "@/components/BillDetail/BillShare";
import { shouldShowDetermination } from "@/utils/should-show-determination/should-show-determination.util";

// Cache individual bill pages.
export const revalidate = PAGE_REVALIDATE_INTERVAL;
// Next.js requires route segment configs to be literal values (not imported constants)
export const revalidate = 120; // seconds - cache individual bill pages

interface Params {
params: Promise<{ id: string }>;
Expand Down Expand Up @@ -170,7 +169,12 @@ export async function generateMetadata(
const defaultOgPath = buildRelativePath(id, "opengraph-image");
const defaultOg = `${baseUrl}${defaultOgPath}`;
const questionsOgPath = q
? buildRelativePath(id, "q", encodeURIComponent(q), "opengraph-image")
? buildRelativePath(
id,
"question",
encodeURIComponent(q),
"opengraph-image",
)
: undefined;
const questionsOg = questionsOgPath
? `${baseUrl}${questionsOgPath}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,12 @@ export const revalidate = 3600;

export default async function QuestionsOpengraphImage({
params,
searchParams,
}: {
params: { id: string };
searchParams: { index?: string };
params: Promise<{ id: string; number: string }>;
}) {
const bill = await getUnifiedBillById(params.id);
const indexParam =
typeof searchParams?.index === "string"
? parseInt(searchParams.index, 10)
: 1;
const { id, number } = await params;
const bill = await getUnifiedBillById(id);
const indexParam = typeof number === "string" ? parseInt(number, 10) : 1;
const index = Number.isFinite(indexParam) && indexParam > 0 ? indexParam : 1;
const question = bill?.question_period_questions?.[index - 1]?.question || "";

Expand All @@ -39,7 +35,7 @@ export default async function QuestionsOpengraphImage({
isSocialIssue: bill?.isSocialIssue || false,
question,
index,
fallbackId: params.id,
fallbackId: id,
}}
/>,
{
Expand Down
13 changes: 5 additions & 8 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ import type { Metadata } from "next";
import { headers } from "next/headers";
import { env } from "@/env";
import { buildRelativePath } from "@/utils/basePath";
import {
BUILD_CANADA_TWITTER_HANDLE,
PROJECT_NAME,
PAGE_REVALIDATE_INTERVAL,
} from "@/consts/general";
import { BUILD_CANADA_TWITTER_HANDLE, PROJECT_NAME } from "@/consts/general";
import FAQModalTrigger from "./FAQModalTrigger";

const CANADIAN_PARLIAMENT_NUMBER = 45;
type HomeSearchParams = { cache?: string };

// Force runtime generation (avoid build-time pre-render) and cache in-memory.
export const dynamic = "auto";
export const revalidate = PAGE_REVALIDATE_INTERVAL;
// Next.js requires route segment configs to be literal values (not imported constants)
export const revalidate = 120; // seconds - cache page data

export async function generateMetadata(): Promise<Metadata> {
const title = "Home";
Expand Down Expand Up @@ -207,9 +204,9 @@ async function getMergedBillsCached(): Promise<BillSummary[]> {
export default async function Home({
searchParams,
}: {
searchParams?: HomeSearchParams | Promise<HomeSearchParams>;
searchParams?: Promise<HomeSearchParams>;
}) {
const resolvedSearchParams = await Promise.resolve(searchParams);
const resolvedSearchParams = searchParams ? await searchParams : undefined;

if (resolvedSearchParams?.cache === "clear") {
mergedBillsCache = null; // Allow manual cache busting with ?cache=clear
Expand Down
4 changes: 3 additions & 1 deletion src/components/BillDetail/BillQuestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ export const BillQuestions = ({
const rawQuestion = q.question ?? "";
// stripMarkdown already handles trimming, so we can use it for both display and sharing
const trimmedQuestion = rawQuestion.trim();
const questionNumber = idx + 1;
const billUrlWithQuestion = `${billUrl}?q=${questionNumber}`;
const shareText = buildXShareText({
title: shareTitle,
question: trimmedQuestion,
url: billUrl,
url: billUrlWithQuestion,
});
const xShareUrl = `https://x.com/intent/post?${new URLSearchParams({ text: shareText }).toString()}`;

Expand Down
9 changes: 7 additions & 2 deletions src/components/OpenGraph/QuestionOgCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function QuestionOgCard({ bill }: { bill: QuestionSubset }) {
>
<div
style={{
display: "flex",
fontSize: 36,
fontWeight: 800,
color: "#0f172a",
Expand Down Expand Up @@ -108,6 +109,7 @@ export function QuestionOgCard({ bill }: { bill: QuestionSubset }) {
>
<div
style={{
display: "flex",
maxWidth: 1040,
fontSize: 44,
fontWeight: 700,
Expand All @@ -120,6 +122,7 @@ export function QuestionOgCard({ bill }: { bill: QuestionSubset }) {
</div>
<div
style={{
display: "flex",
maxWidth: 1040,
fontSize: 28,
color: "#334155",
Expand All @@ -139,10 +142,12 @@ export function QuestionOgCard({ bill }: { bill: QuestionSubset }) {
alignItems: "center",
}}
>
<div style={{ fontSize: 18, color: "#6b7280" }}>
<div style={{ display: "flex", fontSize: 18, color: "#6b7280" }}>
Question Period
</div>
<div style={{ fontSize: 18, color: "#6b7280" }}>{bill.billId}</div>
<div style={{ display: "flex", fontSize: 18, color: "#6b7280" }}>
{bill.billId}
</div>
</div>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/consts/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ export const PROJECT_NAME = "Builder MP";
export const GOOGLE_ANALYTICS_ID = "G-VFXPGBE1PR";
export const BUILD_CANADA_TWITTER_HANDLE = "@buildcanada";

export const PAGE_REVALIDATE_INTERVAL = 120; // All page cache (home, bill details)
export const BILL_API_REVALIDATE_INTERVAL = 600; // Bill API data cache
// Revalidation intervals (in seconds)
// Note: Route segment configs (export const revalidate in page.tsx files) must use literal values
// due to Next.js static analysis requirements. Use these constants only for runtime fetch calls.
export const BILL_API_REVALIDATE_INTERVAL = 600; // Bill API data cache (fetch revalidation)