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.

yourapp.com/

Welcome

Please enter your Prolific ID to begin.

A preview of the entry page you are about to build.

The preview shows the look; the real page adds three behaviors:

  1. 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.
  2. 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.
  3. 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 .env file is the first suspect: the DATABASE_URL line 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 dev is running and you're at localhost: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
}
  • @unique on prolificId means 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) on consented means every new participant starts as not yet consented. The consent lesson flips this to true.
  • createdAt is 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.