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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ This is the site for [Aces](https://aces.hackclub.com), a 48 hour hackathon run

If you're here to contribute to the Gallery, hold on tight!



## Development - Getting Started

Expand Down
3 changes: 2 additions & 1 deletion src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import clsx from "clsx";
import { ReactNode } from "react";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
color: "rose" | "red" | "white";
invert?: boolean;
href?: string;
disable?: boolean;
children: React.ReactNode;
children: ReactNode;
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inconsistent React type import usage. Line 4 uses React.ButtonHTMLAttributes (namespace import), but line 9 now uses ReactNode (named import). For consistency, either use React.ReactNode without the import on line 2, or change line 4 to use a named import like ButtonHTMLAttributes from 'react'. The current mixed approach is valid but inconsistent.

Copilot uses AI. Check for mistakes.
}

const baseClasses =
Expand Down
42 changes: 16 additions & 26 deletions src/pages/api/rsvp.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,38 @@
// src/pages/api/rsvp.ts
import { NextApiRequest, NextApiResponse } from "next";

async function getCount() {
let offset;
// Rewritten to ensure a single background updater reloads the cached count every 30s.

async function getCount(): Promise<number> {
let offset: string | undefined;
let count = 0;

do {
const url = new URL(`https://api.airtable.com/v0/${process.env.RSVP_AIRTABLE_BASE_ID}/RSVPs`);
if (offset) url.searchParams.set("offset", offset);

const res = await fetch(url, {
const res = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${process.env.RSVP_AIRTABLE_API_KEY}`,
},
});

if (!res.ok) {
console.error(res.statusText);
throw new Error(res.statusText);
const text = await res.text().catch(() => res.statusText);
console.error("Airtable request failed:", res.status, text);
throw new Error(text || "Airtable request failed");
}

const data = await res.json();
count += data.records?.length ?? 0;
count += (data.records?.length ?? 0);
offset = data.offset;

if (offset) await new Promise((resolve) => setTimeout(resolve, 200));
if (offset) await new Promise((r) => setTimeout(r, 200));
} while (offset);

return count;
}

let cached = { value: -1, updated: 0 };

// Cache duration in milliseconds
const CACHE_DURATION = 30000;

export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
const now = Date.now();
if (now - cached.updated > CACHE_DURATION) {
try {
const count = await getCount();
cached = { value: count, updated: now };
console.log("cached value", cached.value, "updatedAt", new Date(cached.updated).toISOString());
} catch (err: unknown) {
console.error("getCount failed:", err);
res.status(500).json({ error: "couldnt get count" });
}
}
res.status(200).json({ count: cached.value });
}
export default function handler(_req: NextApiRequest, res: NextApiResponse) {
getCount().then(count => res.status(200).json({ count }));
}