Skip to content

feat(hackaton): páginas del #1 Hackatón NaN (frontend) - #7

Merged
sre-helmcode merged 8 commits into
mainfrom
feat/hackaton-platform
Jun 6, 2026
Merged

sre-helmcode merged 8 commits into
mainfrom
feat/hackaton-platform

Conversation

@Saul-Gomez-J

Copy link
Copy Markdown
Contributor

Qué

Frontend del #1 Hackatón NaN (Astro 6 SSR + Preact islands + Tailwind v4):
landing por fase, inscripción, check-in, panel /me, entrega (submission),
galería de proyectos con votación y leaderboard. Habla con el backend
(nan-cloud-api) vía el proxy interno.

Pantallas / componentes

  • hackaton.astro: landing con CTA según estado del evento y barra de
    inscripciones (aforo + reservas).
  • hackaton/me.astro + ReassignForm: equipo, compañeros y reasignación con
    quórum.
  • hackaton/submission.astro + SubmissionForm: cuestionario editable durante
    building, congelado después. Botón "+ Adjuntar media" con campo de
    vídeo (YouTube) (+ info de subirlo oculto/no listado) y portada.
  • hackaton/projects.astro + VoteButton: galería con portada y enlace
    "▶ Vídeo presentación"; voto recolocable (marca "Tu voto ✓" y permite
    cambiarlo).
  • hackaton/leaderboard.astro: ranking público al cerrar la votación.

Por qué

UI del recorrido completo del hackatón sobre la API del backend (PR de
nan-cloud-api). El leaderboard se mantiene oculto durante la votación (evita
bandwagon) y se revela al cerrar.

Pruebas (las 3 que corre CI)

  • npm test ✓ (143 tests)
  • npx astro check ✓ (0 errores)
  • npm run build ✓

El último commit (fix) tipa las respuestas res.json() (que con
@cloudflare/workers-types se infieren como {}) para que astro check
pase, siguiendo el patrón del repo (await x.json()) as {...}.

🤖 Generated with Claude Code

Saul-Gomez-J and others added 5 commits May 30, 2026 18:06
- Landing /hackaton con CTA dependiente de fase y sesión, + fechas del
  evento (7–13 jun · Demo Day domingo 14) vía i18n es/en.
- Páginas /hackaton/{checkin,me,submission,projects,leaderboard} y
  componentes Preact (RegisterForm, CheckinForm, ReassignForm,
  SubmissionForm, VoteButton).
- Proxy SSR /api/hackaton/[...path] hacia CLOUD_API_URL: preserva la
  cookie de sesión y bloquea admin/* desde el navegador.
- CTA de login apunta al login real de NaN (Discord OAuth en cloud-api,
  /api/auth/discord); no existe página /login propia.
- Tests vitest del proxy.

npm run build en verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n con quórum y submission

Barra de inscripciones (RegistrationBar.astro, hackaton.astro):
- Nuevo componente que muestra plazas ocupadas/restantes antes del formulario,
  con aviso de "pool de reservas" al llenarse las 40 principales y estado
  "inscripciones completas" al llegar a 50. Lee `registered` de /event.

Detección de ya-inscrito y mensajería por fase (hackaton.astro):
- Consulta /me en todas las fases y distingue: no abierto (draft), inscripción
  abierta, ya inscrito, inscripciones cerradas, y panel del participante según
  fase (check-in pendiente / check-in hecho / en curso).
- Interpolación correcta de {cap}/{res} en los textos de capacidad.

Check-in (checkin.astro):
- El frontend refleja cuando el usuario ya hizo check-in (deja de ofrecer la
  opción en bucle).

Reasignación (ReassignForm.tsx, me.astro):
- Botón de información que explica qué es y qué ocurre al convocar.
- UI del quórum=2: muestra "Convocatoria registrada, falta confirmación
  (1/2)", y los resultados filled / no_pool / ya-convocada.

Submission (SubmissionForm.tsx, submission.astro):
- Si el equipo ya tiene submission, prefija los campos, muestra el banner
  "Submission enviada ✓" con los checks persistidos y cambia el botón a
  "Actualizar submission" (evita reenvíos a ciegas).

Login y navegación (LoginForm.tsx, api/auth/login-request.ts, Base.astro,
LoginButton.astro, VoteButton.tsx):
- Flujo de login por magic-link y enlace "Hackatón" en la barra superior.

i18n (es/en): textos de capacidad, fases, reasignación (incl. quórum) y
submission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… congelada

Mejoras de UX en las páginas del hackatón, alineadas con el backend.

Votación (projects.astro, VoteButton.tsx):
- La galería lee el voto actual del usuario (my_vote vía /me con cookie) y
  marca la tarjeta votada con "Tu voto ✓"; el resto muestran "Votar por
  este". Tras votar o cambiar se recarga para dejar un único ✓ coherente.

Landing por fase (hackaton.astro):
- En "voting" el panel del participante enlaza a la galería ("La votación
  está abierta" -> Ver proyectos y votar); en "closed" enlaza al
  leaderboard ("Resultados publicados").

Navegación (me.astro):
- Añade enlace "Volver" a la landing en la pantalla "Mi equipo".

Entrega (submission.astro, SubmissionForm.tsx):
- La entrega solo es editable durante "building"; en fases posteriores se
  muestra congelada en solo-lectura ("plazo cerrado") con sus checks.
- El formulario superficie los errores del backend (validation_failed,
  wrong_state) en vez de fallar en silencio; marca los campos opcionales.

i18n (es/en): claves nuevas para fase (voting/results), submission
(closed/errores) y projects (yourVote/changeVote).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…en votación

Cuestionario de submission: botón "Adjuntar media" que despliega un campo
de vídeo de YouTube (con info que sugiere subirlo oculto/no listado) y un
campo de imagen de portada. La galería de votación muestra la portada
(image_url) y un enlace "▶ Vídeo presentación" (video_url); la vista
congelada lista ambos por separado. Incluye claves i18n es/en.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stro check

Las páginas e islas del hackatón accedían a .data/.ok/.error sobre el
resultado de res.json(), que con @cloudflare/workers-types se infiere como
{} y rompía el type-check de CI. Se castean las respuestas al patrón del
repo ((await x.json()) as {...}) y se exportan FormStrings/LoginStrings
para tipar las llamadas tObj de los formularios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@barckcode

Copy link
Copy Markdown
Contributor

Review — Frontend Hackatón

Full review by the NaN team agents. astro check, vitest (143 tests) and npm run build pass cleanly on pr/7-hackaton. Findings below are grouped by severity. References use file:line on this branch.

🔴 Blockers (must fix before merge)

  1. CSP img-src 'self' data: blocks external project covers and enables data: XSS — src/layouts/Page.astro:39 (also Base.astro:53 / Docs.astro:68). src/pages/hackaton/projects.astro:40 renders <img src={p.image_url}> with participant-controlled URLs. Two consequences:

    • External image_url (Imgur, R2, Cloudinary) is blocked in the browser → gallery breaks at runtime.
    • data: is allowed → a participant can submit data:image/svg+xml,<svg onload=...> and run JS against every voter.
      Fix: tighten to img-src 'self' https: (drop data:); coordinate with backend on host whitelist or proxied images.
  2. Set-Cookie header collapse in the proxy — src/pages/api/hackaton/[...path].ts:41-42 uses headers.get('set-cookie'), which (per WHATWG) collapses multiple Set-Cookie values into one comma-separated string. If the backend ever sets more than one cookie in a single response (login, refresh, onboarding handoff), the browser silently drops all but the first. Fix: use headers.getSetCookie() (available in Workers/undici) and headers.append('set-cookie', value) per entry. Same risk in src/pages/api/auth/login-request.ts if future code starts setting cookies there.

🟡 Important

  • isAdminPath does not normalize paths — src/pages/api/hackaton/[...path].ts:13-21 blocks exact admin / admin/* only. //admin/state, admin//x, mixed case Admin/... and percent-encoded %2fadmin slip through. Astro normalizes some, but defense in depth: lower-case and collapse /+ before the check.
  • Authorization is forwarded unconditionally — src/lib/hackaton.ts:23-24 copies any Authorization header from the browser to the backend. Unnecessary (auth is via nan_session cookie) and risky if a future endpoint trusts both. Remove it from the forwarded list.
  • IP-forwarding header convention inconsistent — src/pages/api/auth/login-request.ts:31-32 rewrites cf-connecting-ip as x-forwarded-for, but the generic proxy (src/lib/hackaton.ts:27-28) forwards cf-connecting-ip as-is. Pick one convention and align both.
  • Sequential SSR fetches across all hackatón pages — src/pages/hackaton.astro:22,50 and the children (me.astro:13,17 does two sequential fetches, submission.astro:15,23, projects.astro:13,21, leaderboard.astro:10) hit the backend serially. Parallelize with Promise.all and consider a short cache for /event.
  • VoteButton reloads the whole page after voting — src/components/hackaton/VoteButton.tsx:22 calls window.location.reload(), causing FOUC and scroll loss on the gallery. Either update local state and dispatch a CustomEvent so the other buttons clear their check, or at minimum location.replace(location.href) to keep history clean.
  • Submission freeze state only checked at SSR time — src/components/hackaton/SubmissionForm.tsx derives canEdit from the SSR snapshot of submission.astro. If the phase changes while the form is open, the user keeps editing; the backend returns wrong_state (mapped correctly to errorClosed in line 61) but the user has already lost the input. Re-poll status on submit or every ~30 s.
  • hasSession heuristic does not detect expired cookies — src/pages/hackaton.astro:41 uses cookie.includes('nan_session'). An expired cookie still triggers /me, returns 401, and the page silently falls back to RegisterForm whose POST will fail. Treat meRes.status === 401 as "no session" and render LoginForm instead.
  • CheckinForm swallows errors with no UI feedback — src/components/hackaton/CheckinForm.tsx:13. Any non-400 error sets state='error' but there is no render branch for that state; the user just sees the checkin button reappear. Add an error message branch.
  • Internal links drop ?lang=en — src/components/hackaton/RegisterForm.tsx:74, submission.astro:46, checkin.astro:16, projects.astro:30,45-50, me.astro:41, leaderboard.astro (no back link at all). i18n users lose locale on every navigation.
  • ReassignForm has hardcoded Spanish strings — src/components/hackaton/ReassignForm.tsx:107,112,115 ("Selecciona ausente", "No se pudo registrar.", "Enviando…"). Move to i18n/{es,en}.json.
  • projects.astro renders p.public_url etc. as <a href> without scheme validation — src/pages/hackaton/projects.astro:45-50 and submission readonly view. Even if the backend is fixed (see backend PR), defense in depth: validate ^https?:// in SSR before rendering.
  • RegistrationBar divides by capacity — src/components/hackaton/RegistrationBar.astro:33-37 assumes capacity > 0. Defensive capacity || 1 if the backend ever returns 0.

🟢 Nits

  • Hydration directives — every island uses client:load. VoteButton in a potentially long gallery (projects.astro:55) is a good candidate for client:visible. ReassignForm (below the fold on /me, only active during building) could be client:idle or client:visible. CheckinForm could be client:idle.
  • SubmissionForm typing — existing?: Existing | null and checks as any (SubmissionForm.tsx:17,28,113). Tighten the type.
  • me.astro:11,16,18 and several siblings — body?.data?.participant typed as any. Define a MeResponse interface.
  • Repeated body.json() as { data?: any } across five pages — extract a helper into lib/hackaton.ts.
  • Accessibility — SubmissionForm.tsx:74-78 and ReassignForm.tsx:107 use placeholder only (no <label>), failing WCAG 1.3.1 / 3.3.2. Add proper labels.
  • RegisterForm.tsx:64 and SubmissionForm.tsx:60-62 — 429 / errorRate not handled in RegisterForm although the backend likely rate-limits.
  • leaderboard.astro — no back link to landing (inconsistent with the rest).
  • Tests are very thin for 1603 LOC — src/tests/api/hackaton.test.ts only covers isAdminPath and backendURL (~10 lines). Missing:
    • The proxy handler end to end (admin → 404, cookie forwarding, multi Set-Cookie propagation, upstream errors).
    • /api/auth/login-request (IP forwarding, error mapping).
    • Form error-code mapping (Register, Submission, Vote).
  • Astro hints (from astro check):
    • src/pages/hackaton/me.astro:11 — unused variable me.
    • src/tests/api/hackaton.test.ts:1 — unused import beforeEach.

Build state

  • npx astro check → ✅ 0 errors, 2 hints (above).
  • npm test (vitest) → ✅ 143/143.
  • npm run build → ✅ clean.
  • npm audit → ⚠️ 14 vulnerabilities (2 high) in deps. Not in scope of this PR but worth a follow-up.

Generated by the NaN review team — happy to clarify any item.

Saul-Gomez-J and others added 3 commits June 4, 2026 12:50
Blockers:
- CSP img-src 'self' data: -> 'self' https: (Page/Base/Docs layouts):
  allow external HTTPS covers and drop the data: SVG XSS vector
- proxy: use Headers.getSetCookie() to forward multiple Set-Cookie
  without comma-collapsing (login + refresh, onboarding handoff)

Security/robustness:
- isAdminPath: normalize path (decode %2f, lowercase, collapse slashes)
- stop forwarding Authorization (auth is cookie-based)
- forward client IP in both CF-Connecting-IP and X-Forwarded-For
- projects: validate URL scheme (http/https only) before rendering

SSR/UX:
- parallelize independent SSR fetches with Promise.all
  (hackaton/me/submission/projects)
- treat /me 401 as expired session -> render LoginForm
- VoteButton: no hard reload; sync islands via CustomEvent('nan:voted')
- SubmissionForm: re-validate phase before submit
- RegistrationBar: guard capacity === 0

i18n/a11y:
- preserve ?lang on internal navigation via withLang() helper
- move hardcoded strings out of ReassignForm into i18n
- CheckinForm: render error state; RegisterForm: handle 429/rate_limited
- add sr-only labels / aria-label to form inputs

Types/tests:
- add Participant/Team/MeData types and jsonData() helper
- type SubmissionForm checks; drop remaining any
- tune hydration directives (visible/idle)
- expand tests: admin normalization, proxy e2e (404/cookie/multi
  Set-Cookie/500), login-request IP forwarding

Verified: astro check (0/0/0), npm test (148 passing), npm run build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Acompaña la eliminación del check-in en el backend: el participante
inscrito pasa directo a la fase de equipos, sin pantalla de confirmación.

- borrados checkin.astro y CheckinForm.tsx
- hackaton.astro: panel sin ramas de check-in
- lib/hackaton.ts: fuera checkin_at del tipo Participant
- i18n es/en: quitadas claves phase.checkin*, el bloque checkin y limpiados
  poolExplain y reassignInfo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three blocking issues from PR review:

- SubmissionForm: replace sr-only-placeholder pattern with visible labels
  consistent with RegisterForm (labelCls + violet uppercase). Field context
  stays visible while typing — WCAG 1.3.1 + 3.3.2.
- SubmissionForm: narrow b.data and existing into Checks via toChecks()
  runtime validator. Removes unsafe `as unknown as Checks` cast and `any`
  destructuring; UI no longer breaks silently if the backend response shape
  drifts.
- projects.astro: meaningful alt on project cover image
  (`title — team_name`) — WCAG 1.1.1 for the vote gallery flow. Also adds
  `decoding="async"`.

Tests: 148 pass, astro check 0 errors, npm run build clean.
@sre-helmcode
sre-helmcode merged commit 33836b1 into main Jun 6, 2026
1 check passed
@sre-helmcode
sre-helmcode deleted the feat/hackaton-platform branch June 6, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants