diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b89f4bd..1df729c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1 @@ -# Default owners for everything in the repo. -# These maintainers are requested for review on every pull request. * @eluciano11 @rnegron diff --git a/CLAUDE.md b/CLAUDE.md index 532e8a6..fdec86f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Commands -Node version is pinned via `.nvmrc` (currently `22.22.2`, lts/jod). Use `nvm use` before running anything. +Node version is pinned via `.nvmrc`. Use `nvm use` before running anything. - `npm run develop` — start the Gatsby dev server (`gatsby develop`); also aliased as `npm start` - `npm run build` — production build into `public/` diff --git a/gatsby-config.js b/gatsby-config.js index 2b6ce87..5724cd6 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -24,7 +24,7 @@ module.exports = { background_color: `#0282f8`, theme_color: `#0282f8`, display: `standalone`, - icon: `src/images/fsn.png` // This path is relative to the root of the site. + icon: `src/images/fsn.png` } }, `gatsby-plugin-postcss`, diff --git a/netlify/functions/submission-created.js b/netlify/functions/submission-created.js index 25807db..9778e2c 100644 --- a/netlify/functions/submission-created.js +++ b/netlify/functions/submission-created.js @@ -1,19 +1,9 @@ -// Netlify calls this automatically on every verified form submission -// (spam/honeypot hits are filtered out before this runs). It forwards the -// submission to a private Discord channel via an incoming webhook. -// -// This is an event-triggered function (named after the "submission-created" -// event), so Netlify invokes it internally — it is not exposed as a public -// HTTP endpoint. Set DISCORD_WEBHOOK_URL in Netlify → Environment variables. - -// null-prototype map so a form-controlled name can't resolve to an inherited -// Object.prototype member (e.g. "toString", "constructor"). +// Null prototype: form names are user-controlled. const TITLES = Object.assign(Object.create(null), { "speaker-submission": "🎤 New speaker / topic submission", feedback: "💬 New feedback" }); -// Netlify metadata we don't want to echo into Discord. const HIDDEN_FIELDS = new Set(["bot-field", "form-name"]); exports.handler = async (event) => { diff --git a/src/components/layout.js b/src/components/layout.js index 172f295..8dae9dd 100644 --- a/src/components/layout.js +++ b/src/components/layout.js @@ -1,10 +1,3 @@ -/** - * Layout component that queries for data - * with Gatsby's useStaticQuery component - * - * See: https://www.gatsbyjs.org/docs/use-static-query/ - */ - import React from "react"; import PropTypes from "prop-types"; diff --git a/src/components/netlify-honeypot.js b/src/components/netlify-honeypot.js index 42643c1..0e47866 100644 --- a/src/components/netlify-honeypot.js +++ b/src/components/netlify-honeypot.js @@ -1,8 +1,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; -// Shared Netlify form plumbing: the hidden form-name (must match the
's -// name) plus the honeypot field that traps bots. Used by every Netlify form. +// formName must match the 's name attribute. function NetlifyHoneypot({ formName }) { const { t } = useTranslation(); diff --git a/src/components/sponsors.js b/src/components/sponsors.js index 2244234..b5b5267 100644 --- a/src/components/sponsors.js +++ b/src/components/sponsors.js @@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next"; import PageSection from "./page-section"; -// Hidden unless an active event defines `event.sponsors`. function Sponsors({ sponsors = [] }) { const { t } = useTranslation(); diff --git a/src/components/submission-form.js b/src/components/submission-form.js index 6155858..a9c2fd3 100644 --- a/src/components/submission-form.js +++ b/src/components/submission-form.js @@ -5,7 +5,6 @@ import NetlifyHoneypot from "./netlify-honeypot"; const FORM_NAME = "speaker-submission"; -// Maps the ?type= query param (set by each page's CTA) to a pre-selected intent. const INTENT_BY_TYPE = { talk: "Present a talk", table: "Host a topic table", @@ -36,15 +35,12 @@ function SubmissionForm() { const [status, setStatus] = useState("idle"); const successRef = useRef(null); - // Move focus to the confirmation so screen-reader users are told the outcome. useEffect(() => { if (status === "success" && successRef.current) { successRef.current.focus(); } }, [status]); - // Pre-select the intent based on which CTA the visitor arrived from. Done in - // an effect (not initial state) so it doesn't fight server-side hydration. useEffect(() => { const type = new URLSearchParams(window.location.search).get("type"); if (INTENT_BY_TYPE[type]) setIntent(INTENT_BY_TYPE[type]); diff --git a/src/config.js b/src/config.js index 46915e6..6d47987 100644 --- a/src/config.js +++ b/src/config.js @@ -1,58 +1,6 @@ -// Luma keeps the street address guests-only, so the venue links to the event -// page rather than to a map. -const LUMA_EVENT_URL = "https://luma.com/s9jx3jri"; - const CONFIG = { - activeEvent: true, - // When activeEvent is true, `endDate`, `agenda`, and `sponsors` are optional; - // everything else is required. - // date/endDate are local wall-clock ISO strings (no offset) so every visitor - // sees the venue's time, not their own. - // agenda drives the /schedule/ timeline; omit it to use the defaults below. - // sponsors: [{ name, logo, url }] renders a Sponsors section on the home page. - event: { - type: "speakers", - date: "2026-09-10T18:00:00", - endDate: "2026-09-10T21:00:00", - registrationUrl: LUMA_EVENT_URL, - agenda: { - kickOffMinutes: 20, - firstTalkMinutes: 30, - // 15 min talk + 5 min Q&A. - talkMinutes: 20 - }, - venue: { - name: "Bayamón, Puerto Rico", - location: LUMA_EVENT_URL - }, - participants: [ - { - name: "Giovanni Collazo", - topic: "jj es más fácil que git", - img: "/founders/giovanni-collazo.jpeg", - links: [{ network: "github", url: "https://github.com/gcollazo" }] - }, - { - name: "Christian Rodríguez", - topic: "Tus agents necesitan acceso (seguro) a producción", - img: "/organizers/christian-rodriguez.jpeg", - links: [{ network: "github", url: "https://github.com/chrisrodz" }] - }, - { - name: "Raúl Negrón-Otero", - topic: "Un vistazo a Django Template Partials", - img: "/organizers/raul-negron.jpeg", - links: [{ network: "github", url: "https://github.com/rnegron" }] - } - ], - sponsors: [ - { - name: "PostHog", - logo: "/sponsors/posthog.svg", - url: "https://posthog.com/" - } - ] - } + activeEvent: false, + event: {} }; export default CONFIG; diff --git a/src/date.js b/src/date.js index cfa6ee9..e8edc00 100644 --- a/src/date.js +++ b/src/date.js @@ -7,19 +7,14 @@ function isSpanish(language) { return resolveLanguage(language) === "es"; } -// "Thursday, September 10th, 2026" / "jueves, 10 de septiembre de 2026" export function formatFullDate(date, language) { return format(date, "PPPP", { locale: isSpanish(language) ? es : enUS }); } -// "6:00pm" — Puerto Rico uses 12-hour time in both languages, and date-fns -// spells the day period the same way in each, so this needs no locale. export function formatTime(date) { return format(date, "h:mmaaa"); } -// "Sep 10" / "10 sep". date-fns has no localized month-and-day-only token, so -// the field order is spelled out per language. export function formatShortDay(date, language) { const spanish = isSpanish(language); diff --git a/src/language.js b/src/language.js index 70926b7..e4f3ba0 100644 --- a/src/language.js +++ b/src/language.js @@ -1,6 +1,3 @@ -// i18next hands out full tags ("en-US", "es-PR", and whatever the browser -// detector reports), but the site ships exactly two locales. Every "which -// language is this?" decision routes through here so the answer can't drift. export function resolveLanguage(language) { return language?.startsWith("es") ? "es" : "en"; } diff --git a/src/pages/schedule.js b/src/pages/schedule.js index f79a6d5..3b7ae25 100644 --- a/src/pages/schedule.js +++ b/src/pages/schedule.js @@ -43,7 +43,6 @@ function buildTimeline(event, t, language) { header: t("schedule.introduction-and-kick-off"), subheader: t("schedule.grab-your-seat") }, - // Slots stay unnamed so the schedule doesn't commit to a speaking order. ...event.participants.map((_, index) => ({ dateTime: at(firstTalkMinutes + index * talkMinutes), header: t("schedule.presentation", { number: index + 1 }) @@ -76,8 +75,6 @@ export default function Schedule() { const { t, i18n } = useTranslation(); const { activeEvent, event } = CONFIG; - // The nav hides /schedule/ off-season, but the route stays reachable by - // bookmark — point those visitors at the calendar, not an empty timeline. if (!activeEvent) { return ( diff --git a/src/past-speakers.js b/src/past-speakers.js index 2831f61..0a2d4fe 100644 --- a/src/past-speakers.js +++ b/src/past-speakers.js @@ -1,7 +1,7 @@ const PAST_SPEAKERS = [ { name: "Axel Rivera", link: "", img: "/past-speakers/axel-rivera.jpeg" }, { - name: "Jonathan González", // Not found + name: "Jonathan González", link: "", img: "/past-speakers/jonathan-gonzalez.jpeg" }, diff --git a/src/styles/global.css b/src/styles/global.css index 44dfc49..1d064ef 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -106,8 +106,6 @@ svg { transition: width 0.3s; } -/* Default form styles */ -/* Feedback form overrides */ .feedback__input { @apply border border-white-hover px-2 py-2 mb-3 w-full rounded resize-none; border-width: 1px;