Skip to content

feat(admin): recruitment console (PR A of 3) - #205

Merged
barunaniket merged 1 commit into
codechefPesuecc:mainfrom
ishanshaurya:feat/recruitment-admin
Sep 8, 2026
Merged

feat(admin): recruitment console (PR A of 3)#205
barunaniket merged 1 commit into
codechefPesuecc:mainfrom
ishanshaurya:feat/recruitment-admin

Conversation

@ishanshaurya

Copy link
Copy Markdown
Contributor

Admin console for the recruitment drive — open/close it and set the Google
Form /join embeds, no redeploy needed. Depends on #203 (merged) — already
rebased onto main.

What's here

  • PATCH /api/admin/recruitment — admin-gated, backed by updateRecruitmentSettings()
  • /admin/recruitment page + RecruitmentPanel — mirrors admin/users + UserManagementPanel
  • admin/layout.tsx + AdminNav — shared nav, extracted from the duplicated blocks in
    admin/page.tsx and admin/users/page.tsx. Visible side effect: admin/teachers,
    admin/problems/new, and admin/problems/[slug]/edit now show this nav too (they had
    none, or in teachers' case were missing it).

The layout is presentational only — every page keeps its own getAdminUser() +
redirect("/") and its own export const dynamic = "force-dynamic"; the route handler
is gated independently of the page.

Verified

  • npm run test / tsc --noEmit / lint / build all clean
  • Manually in-browser: nav consistent across all 5 admin pages, URL save + reload
    persistence, bad-URL rejected with inline error and no write, partial save (toggle
    alone) doesn't clobber the URL field, gate bounces both signed-out and signed-in
    non-admin users to /

Adds /admin/recruitment where an admin opens/closes the recruitment
drive and sets the Google Form URL /join embeds, with no redeploy.

- src/app/api/admin/recruitment/route.ts — PATCH, admin-gated, backed
  by updateRecruitmentSettings() from PR 0. Returns the settings as
  actually persisted (not an echo of the request), so the client syncs
  to server truth.
- src/app/admin/recruitment/page.tsx + RecruitmentPanel.tsx — server
  page + client form, mirroring admin/users + UserManagementPanel.
  Save sends only the fields the admin changed — the backend does a
  partial merge, so sending untouched fields too would silently
  overwrite whatever another admin (or another tab) saved to them
  since this panel loaded.
- src/app/admin/layout.tsx + AdminNav.tsx — lifts the nav shared by
  all five admin pages into one place. Presentational only: every page
  keeps its own getAdminUser()/redirect("/") gate and its own
  `export const dynamic = "force-dynamic"`, and the route handler is
  gated independently of the page.

Visible side effect: admin/teachers, admin/problems/new, and
admin/problems/[slug]/edit now show the nav too (they had none, or in
teachers' case were accidentally missing it).

Depends on PR 0 (codechefPesuecc#203, feat/recruitment-foundation) — branched off it
since codechefPesuecc#203 hasn't merged yet; rebase onto main once it does.

Verified locally (npm run dev): admin/non-admin/anonymous gating on
both the page and the route, bad-URL rejection with no write, real-URL
persistence across reload, partial-PATCH doesn't clobber other fields,
identical nav on all five admin pages. `npm run test`, `npx tsc
--noEmit`, `npm run lint`, `npm run build` all clean.
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

@ishanshaurya is attempting to deploy a commit to the barunaniket-9400's projects Team on Vercel.

A member of the Team first needs to authorize it.

@barunaniket barunaniket left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving. The part that mattered most here is right, and it's the part that was easiest to get wrong.

Security review of the nav extraction

The risk in this PR was the layout swallowing the auth gate. It doesn't:

  • All six /admin/* pages keep their own getAdminUser() + redirect("/") and their own export const dynamic = "force-dynamic". Verified individually, including the three that only inherit the nav (teachers, problems/new, problems/[slug]/edit) — the diff removed markup and nothing else.
  • PATCH /api/admin/recruitment gates independently of the page, which is what matters for a handler reachable by direct POST.
  • The comment in layout.tsx explaining why the layout can't be the boundary is the right thing to leave behind for whoever touches this next.

Things you did that weren't asked for and should stay

  • The panel sends only changed fields. Two admins in two tabs won't silently overwrite each other's untouched values. The comment explaining it is accurate.
  • Syncing state from the server's persisted response instead of echoing the request. Correct for updatedAt/updatedBy and for any server-side normalization.
  • Disabling Save when !dirty, and keeping the form dirty after a failed save so a retry is possible.

Checked and deliberately not flagged

No enforceRateLimits on the new route — but no existing route under src/app/api/admin/** uses it either, so this matches the codebase rather than deviating from it. Fine as is.

Cosmetic, not blocking — I'll take these in a follow-up

  1. Double top padding on the three inherited pages. admin/page.tsx and admin/users/page.tsx had pt-6 dropped from their section (correctly, since the layout now supplies it), but teachers, problems/new and problems/[slug]/edit still carry pt-6 — so those three get the gap twice.
  2. Nav width mismatch on the problem pages. The layout wrapper is max-w-4xl; problems/new and problems/[slug]/edit are max-w-3xl, so the nav sits wider than the content it heads.

Neither is worth another round trip on your branch — merging this and fixing both in a follow-up.

Optional, from the plan, if you'd rather pick them up than have me: a preview link that opens the saved form in a new tab (so an admin can confirm the paste before opening the drive), and a plain-language status line — "Recruitment is open. /join is showing the form."

@barunaniket
barunaniket merged commit 74628a1 into codechefPesuecc:main Sep 8, 2026
1 of 2 checks passed
barunaniket added a commit that referenced this pull request Sep 8, 2026
Follow-up to #205. Extracting the nav into admin/layout.tsx left three pages
that only inherit it out of step with the two that were edited.

- teachers, problems/new and problems/[slug]/edit still carried `pt-6` on
  their own section while the layout now supplies it, so those three rendered
  the gap twice.
- The two problem pages were `max-w-3xl` against the layout's `max-w-4xl`, so
  the nav sat wider than the content it heads. Widened them rather than
  narrowing the nav: all six console pages now share one measure, which is the
  point of having a shared layout. It does make the problem-authoring form
  slightly wider — deliberate, and admin-only.

Also picks up the two panel touches from the plan that #205 left optional:

- A "Preview the saved form" link, so an admin can confirm the URL they pasted
  is the right form *before* opening the drive to applicants.
- A plain-language line saying what /join is actually serving right now.
  Derived from the saved row rather than the editor fields on purpose — reading
  the unsaved state would announce a drive as open the moment someone ticked
  the box, before anything was written.
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.

2 participants