Prolific ID Entry Page
Overview
We are going to build the Prolific ID entry page. This is the first page a participant sees when they arrive from Prolific. It asks them to type their Prolific ID into a text field and submit it before anything else happens.
By collecting the Prolific ID on the very first page, before anything else happens, you guarantee that every person who enters the experiment has a record in your database. Incomplete sessions are still useful data. You can see drop-out rates, where participants are leaving, and whether any ID shows up more than once. That last point matters: if the same participant tries to submit twice, you can detect it and show them a message explaining their ID has already been used instead of creating a duplicate record.
What You're Building
This is a preview of the page we are building.
Welcome
Please enter your Prolific ID to begin.
The preview shows the look; the real page adds three behaviors:
- A new ID gets saved. When a participant enters an ID and clicks Begin, they are recorded in the database and moved on to the consent form.
- A repeated ID gets caught. If the same ID is entered again, the page stays put and shows a message instead of creating a second record. One person, one record, enforced by the database itself.
- An empty submission does nothing. The form refuses until there's something in the box.
Building It With Claude Code
Open Claude Code in your project folder. Before you paste anything, one piece
of syntax to know: the @ symbol attaches a specific file to your prompt.
Claude Code reads that file and works with what's already there instead of
inventing from scratch. For this page, two files matter:
@prisma/schema.prisma (what the database stores) and @src/app/page.tsx
(the entry page itself).
Here is the prompt. Paste it as is, or reword it however feels natural:
@prisma/schema.prisma @src/app/page.tsx
I need to build a Prolific ID entry page. Here is what it should do:
1. Add a Participant model to the schema with these fields: a unique
prolificId string, a createdAt timestamp set automatically, and a
consented boolean that defaults to false.
2. Update page.tsx to show a welcome message and a text input where the
participant types their Prolific ID and clicks Begin. Empty
submissions should do nothing.
3. When they submit, check if that ID already exists in the database. If
it does, stay on the entry page and show a message that the ID has
already been used. If it's new, save a Participant record, remember
the participant with a cookie, and redirect to /consent.
After updating the schema, run the migration (name it add-participant)
and regenerate the Prisma client.
What to expect while it works. Claude Code will read the two files,
propose edits, and ask your permission before changing anything or running
commands. Approve the schema edit, the page edit, and the migration command.
The migration is the step that actually creates the Participant table in
your Neon database, so don't skip it. When Claude Code finishes, it will
summarize what it did. Read that summary; it's how you stay in charge.
A few things to notice about the prompt itself:
- You describe outcomes, not code. Nothing in that prompt says how to implement anything. It says what the participant should experience and what should happen to the data. Claude Code decides the rest.
- The
@files are the starting point. Without them, Claude Code doesn't know what your schema looks like or what's already on the page. - You can be as specific or as vague as you want. Strong opinions about wording, layout, or field names? Put them in. No opinions? Leave them out and revise what comes back.
Testing What You Built
With npm run dev running, go to http://localhost:3000. You should see the
welcome message and the text input, just like the preview above. Type any ID
and click Begin.
You will land on a 404 error page. That's correct. The redirect to
/consent worked; the consent page just doesn't exist yet, because we
haven't built it. Think of it like following directions to a building that
hasn't been constructed: the directions are right, the building isn't there
yet. Next lesson we pour that foundation.
Now confirm the data actually made it in. Open a second terminal and run:
npm run db:studio
This opens Prisma Studio, a visual window into your database. Check the
Participant table: there should be one row with the ID you typed, a
createdAt timestamp, and consented: false. Notice what just happened:
you ended up on an error page, and the ID still made it into the database.
That's because the page saves the ID first and moves the participant along
second. Once the ID is written down, nothing that goes wrong afterward can
lose it.
Finally, go back to http://localhost:3000 and submit the same ID again. You
should stay on the page and see the "already been used" message, exactly like
the preview. Check Prisma Studio: still one row, not two.
If You Get Stuck
The pattern from the architecture lesson applies here: tell Claude Code what you did, what you expected, and what happened instead. Paste any red error text directly into the prompt. Some specific symptoms and where they usually point:
- The migration failed with an error about reaching the database. Your
.envfile is the first suspect: theDATABASE_URLline may be missing, still holding the placeholder, or carrying a typo from the copy-paste. Tell Claude Code: "The migration failed, here is the error: [paste]. Walk me through checking my database connection setup." - The page still shows the old welcome screen. Make sure
npm run devis running and you're atlocalhost:3000. If both check out, tell Claude Code: "I'm on localhost:3000 but I still see the starter welcome page instead of the ID form." - Clicking Begin does nothing at all. "I type an ID and click Begin and nothing happens, no redirect and no error. Find out why."
- You reached the error page, but nothing shows up in Prisma Studio. Pay extra attention to this one: the page moved you along, but the ID was never written to the database. In a real study, that would mean losing participants without knowing it. "I entered an ID and clicked Begin, but the Participant table in Prisma Studio is empty. The ID is not being saved to the database. Find out why."
- Submitting a duplicate ID creates a second row. "Entering the same Prolific ID twice creates two rows in the Participant table. It should show an already-used message instead. Fix the duplicate check."
Notice the shape of every one of those prompts: symptom, expectation, and a pointer at the right layer. You're not fixing code. You're directing the troubleshooting the same way you directed the build.
Make It Your Own
Before moving on, take a few minutes to change how the page looks using Claude Code. This is a low-stakes way to practice the prompt-and-revise loop before the stakes are higher.
Try a prompt like this:
@src/app/page.tsx
Change the background color of the page to a soft blue. Make the heading
larger and change the button color to match.
See what it produces. If you don't like something, describe what you'd change:
@src/app/page.tsx
The button color is too dark. Make it lighter and add a subtle border.
You don't need to understand the styling code to do this. You're practicing the skill of describing what you see and what you want to be different. That same skill is what you'll use to describe participant flows, data structures, and task logic throughout the rest of the course.
There's no right answer here. When the page looks the way you want it, move on.
The Code, If You're Curious
You don't need to read anything in this section to continue the course. Claude Code wrote the implementation, and yours may differ slightly from this one in wording or styling while doing exactly the same job. It's here for the curious and for anyone who wants to compare against the checkpoint branch.
The schema: what the database stores
model Participant {
id String @id @default(cuid()) // unique internal ID, generated automatically
prolificId String @unique // the ID the participant types in; must be unique
createdAt DateTime @default(now()) // timestamp of when they entered the experiment
consented Boolean @default(false) // updated to true after they complete the consent form
}@uniqueonprolificIdmeans the database itself will reject a second record with the same ID. Duplicate prevention lives at the data level, not just in the page.@default(false)onconsentedmeans every new participant starts as not yet consented. The consent lesson flips this to true.createdAtis stamped automatically when the row is created.
The page: the form and the save logic
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
import prisma from "@/lib/prisma";
async function submitId(formData: FormData) {
"use server";
const prolificId = formData.get("prolificId") as string;
if (!prolificId || prolificId.trim() === "") {
return;
}
const existing = await prisma.participant.findUnique({
where: { prolificId: prolificId.trim() },
});
if (existing) {
redirect("/?error=duplicate");
}
const participant = await prisma.participant.create({
data: { prolificId: prolificId.trim() },
});
(await cookies()).set("participantId", participant.id, {
httpOnly: true,
path: "/",
});
redirect("/consent");
}
export default async function Home(props: {
searchParams: Promise<{ error?: string }>;
}) {
const { error } = await props.searchParams;
return (
<div className="flex flex-col flex-1 items-center justify-center bg-white dark:bg-black px-6">
<div className="flex flex-col items-center text-center gap-6 max-w-lg">
<h1 className="text-3xl font-bold text-zinc-900 dark:text-white">
Welcome
</h1>
<p className="text-zinc-500 dark:text-zinc-400 text-lg">
Please enter your Prolific ID to begin.
</p>
{error === "duplicate" && (
<p className="text-red-600 dark:text-red-400 text-sm">
This Prolific ID has already been used for this study. If you
believe this is a mistake, please contact the research team through
Prolific.
</p>
)}
<form action={submitId} className="flex flex-col gap-4 w-full">
<input
type="text"
name="prolificId"
placeholder="Your Prolific ID"
className="border border-zinc-300 dark:border-zinc-700 rounded px-4 py-3 text-zinc-900 dark:text-white dark:bg-zinc-900 w-full"
/>
<button
type="submit"
className="bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 font-semibold px-6 py-3 rounded"
>
Begin
</button>
</form>
</div>
</div>
);
}The short version of what it does: submitId runs on the server when the
form is submitted (that's the backend from the architecture lesson). It looks
up the typed ID, and either sends the participant back with the duplicate
message or creates the record, sets a cookie so later pages know who this is,
and redirects to consent. Everything the participant sees lives in the
returned layout below it (the frontend).
🌿 Checkpoint branch: 02-prolific-id-page: This is the finished entry
page. If you check this branch out, you will see the finished entry page and
be completely caught up to this point in the course.
⚠️ If you built earlier lessons yourself and then switch to this
branch: your database was set up by your own migrations, not this
branch's, so npm run db:migrate will report a mismatch and offer to
reset the database. Accepting the reset is the right move during the
course: it deletes your practice data and rebuilds the tables to match
this branch. Never accept a reset once real participant data is in the
database.