Build a Scam Detection Form with React

Build a detective-themed scam detection form using React components and state.

Introduction

30 Second Summary

Every day, millions of people receive suspicious messages asking them to click a link or hand over personal details. Spotting these scams quickly can save someone from losing money or private data.

In this project, you will build a scam detection form page using React. Users paste a suspicious message, click a button, and see a mock analysis result telling them whether the message looks risky.

What You'll Build

A detective-themed React form page where users submit suspicious messages and receive a mock scam verdict with confidence scores and evidence.

By the end of this project, you'll have:

  • A ScamForm component with a textarea, text input, and submit/clear buttons that handle user events.
  • A ResultCard component that conditionally appears with a mock scam analysis verdict, confidence score, and evidence list.
  • A styled dark detective-themed UI with gold accents, matching your team's design mockup.
  • Secret Mission: Add clickable sample message links that auto-fill the form with example scams.

Are there any prerequisites?

No prior React experience is needed. Very little JavaScript knowledge is assumed. You just need basic computer literacy and a willingness to follow along step by step.

Before We Start

Take a moment to think about what you'll be building in this project before diving in.

Set Up the Vite + React Project

To build the Detect page, you need a React development environment on your machine.

You'll use Vite, a fast build tool, to scaffold a React project. This gives you a ready-made setup where you can write components, see live changes in the browser, and hand off your code to your team.

In this step, get ready to:
  • Verify your Node.js installation.
  • Create and start a Vite + React project.
  • Clean up the default scaffold and set up your folder structure.
Verify your Node.js version

Vite requires Node.js version 20.19 or higher to run. Let's confirm your installation is ready.

  • In VS Code, open a terminal by clicking Terminal in the top menu bar, then New Terminal.
  • Check your Node.js version by running this command:
node --version
  • You should see a version number like v20.19.0 or higher.

Seeing a version lower than 20.19, or command not found?

Download the LTS version from https://nodejs.org and install it. After installing, close and reopen your terminal, then run node --version again.

Help me troubleshoot my Node.js installation.

Create the Vite + React project

Vite scaffolds a complete React project in seconds. It generates the file structure, installs React, and sets up a development server that refreshes your browser every time you save a file.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create a new Vite + React project by running these commands:
npm create vite@latest scam-detector -- --template react
cd scam-detector
npm install

What do these commands do?

  • npm create vite@latest scam-detector -- --template react scaffolds a new project folder called scam-detector with React files ready to go.
  • cd scam-detector moves your terminal into the new folder.
  • npm install downloads all the packages React and Vite need to run.

💡 npm asks to install a package first?

If you see a prompt saying "Need to install the following packages", type y and press Enter to continue. This is normal for the first time you run the command.

  • Start the development server by running:
npm run dev
  • Open http://localhost:5173 in your browser.
  • You should see the default Vite + React welcome page with a spinning logo and a counter button.

Don't see the welcome page?

Make sure you ran npm install before npm run dev. Without the install step, React's packages are missing and the server cannot start.

Check that your terminal is inside the scam-detector folder. If the terminal still points at your Desktop, run cd scam-detector first.

Help me troubleshoot why my Vite dev server isn't starting.

💡 Why Vite instead of Create React App?

Create React App was the old default for starting React projects, but it was officially deprecated in February 2025. Vite is now the recommended build tool for from-scratch React apps.

Vite starts faster, refreshes changes instantly, and produces smaller production builds. For a single-page form like your Detect page, a lightweight tool like Vite is all you need.

Clean up the scaffold and create your folder structure

Vite generates demo code to show off its features. You'll clear that out and set up the folder structure your team uses.

  • In VS Code, open the scam-detector folder by clicking File in the top menu bar.
  • Click Open Folder.
  • Select the scam-detector folder on your Desktop, then click Open.

You should see the project files in the Explorer sidebar on the left.

  • In the Explorer sidebar, click src/App.css to open it.
  • Select all the text with Cmd+A (macOS) or Ctrl+A (Windows), then press Delete.
  • Save the empty file with Cmd+S (macOS) or Ctrl+S (Windows).
  • Do the same for src/index.css. Open it, select all, delete, and save.

Now replace the demo component in App.jsx with a simple placeholder.

  • Open src/App.jsx in the Explorer sidebar.
  • Select all the existing code and replace it with:
function App() {
  return <div>Scam Detector</div>
}

export default App

What does this code do?

This defines a React component called App that returns a single <div> with the text "Scam Detector".

The export default App line makes this component available to other files. Right now, main.jsx imports it and renders it to the page.

  • Save src/App.jsx.
  • Check your browser at http://localhost:5173. You should see the text "Scam Detector" on a blank page.

Still seeing the old Vite welcome page?

Make sure you saved both App.jsx and App.css. Unsaved changes won't appear in the browser.

If the page still shows the spinning logo, check that you replaced ALL the code in App.jsx and didn't leave any of the original demo imports at the top of the file.

Help me figure out why my App.jsx changes aren't showing in the browser.

Finally, create the folders your team's structure expects.

  • Open the terminal in VS Code (use the one already running or click Terminal then New Terminal).
  • Create the page and component folders by running these commands:
mkdir -p src/pages/Detect
mkdir -p src/components/ScamForm
mkdir -p src/components/ResultCard

What do these folders hold?

  • src/pages/Detect/ holds the main Detect page that brings all your components together.
  • src/components/ScamForm/ holds the form where users paste a suspicious message.
  • src/components/ResultCard/ holds the card that displays the scam analysis results.
  • Confirm the folders exist by running:
ls src/pages src/components
  • You should see Detect listed under src/pages, and ScamForm and ResultCard listed under src/components.

Folders not showing up?

Make sure your terminal is inside the scam-detector folder. If you opened a new terminal, it might have landed in a different directory. Run pwd to check, and cd ~/Desktop/scam-detector to get back.

Help me figure out why my mkdir commands didn't create the folders I expected.

✔️ Awesome, I've got everything!

Great. Double check you've saved all your files, then continue to the next step.

ⓧ I'd like to double check the full code

Here is what your src/App.jsx should look like right now:

function App() {
  return <div>Scam Detector</div>
}

export default App

Your src/main.jsx should be the default Vite entry point (you did not change this file):

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Your src/App.css and src/index.css should both be completely empty.

Your React environment is ready and your folder structure matches the team's conventions. Next up, you'll build the ScamForm component where users paste suspicious messages.

Build the ScamForm Component

Your project is scaffolded and the dev server is running. Now it's time to build the first real piece of your scam detection page.

In React, you split a page into smaller, reusable pieces called components. Your first component will be the ScamForm, a form where users paste a suspicious message and optionally add a link.

In this step, get ready to:
  • Create a ScamForm component that renders a form with a textarea, input, and two buttons.
  • Style the form with basic structural CSS.
  • Wire the component into a Detect page and display it in the browser.
Create the ScamForm component file

A React component is a function that returns JSX, which looks like HTML but lives inside JavaScript. This component will accept props (values and functions passed in from a parent) so that the parent page can control what the form displays and what happens when the user interacts with it.

  • Create a new file at src/components/ScamForm/ScamForm.jsx and add the following code:
import './ScamForm.css'

function ScamForm({ message, link, onMessageChange, onLinkChange, onSubmit, onClear }) {
  return (
    <div className="scam-form">
      <label className="form-label">THE MESSAGE IN QUESTION</label>
      <textarea
        className="form-textarea"
        placeholder="Paste the suspicious message here..."
        value={message}
        onChange={(e) => onMessageChange(e.target.value)}
      />

      <label className="form-label">ANY LINK IT ASKED YOU TO OPEN</label>
      <input
        className="form-input"
        type="text"
        placeholder="https://... (optional)"
        value={link}
        onChange={(e) => onLinkChange(e.target.value)}
      />

      <div className="form-buttons">
        <button className="btn-submit" onClick={onSubmit}>
          OPEN THE CASE FILE
        </button>
        <button className="btn-clear" onClick={onClear}>
          CLEAR
        </button>
      </div>
    </div>
  )
}

export default ScamForm

What does this code do?

The function ScamForm destructures six props from its parent: two values (message and link) and four callback functions (onMessageChange, onLinkChange, onSubmit, onClear).

The value attribute on the textarea and input makes them controlled components. React owns their content, and onChange fires whenever the user types to update the parent's state.

Each button's onClick calls the corresponding prop function. The component itself stores nothing. It just renders whatever the parent tells it to.

You won't see a change in the browser yet. This file is not connected to your app's dependency tree until you import it in a page.

Seeing a red squiggly in VS Code?

If your editor shows an error on the import './ScamForm.css' line, that is expected. The CSS file does not exist yet. You will create it in the next substep.

help me understand why VS Code shows an import error for a file I haven't created yet

✔️ Awesome, I've got everything!

Great. Make sure you have saved ScamForm.jsx before moving on.

ⓧ I'd like to double check the full code

import './ScamForm.css'

function ScamForm({ message, link, onMessageChange, onLinkChange, onSubmit, onClear }) {
  return (
    <div className="scam-form">
      <label className="form-label">THE MESSAGE IN QUESTION</label>
      <textarea
        className="form-textarea"
        placeholder="Paste the suspicious message here..."
        value={message}
        onChange={(e) => onMessageChange(e.target.value)}
      />

      <label className="form-label">ANY LINK IT ASKED YOU TO OPEN</label>
      <input
        className="form-input"
        type="text"
        placeholder="https://... (optional)"
        value={link}
        onChange={(e) => onLinkChange(e.target.value)}
      />

      <div className="form-buttons">
        <button className="btn-submit" onClick={onSubmit}>
          OPEN THE CASE FILE
        </button>
        <button className="btn-clear" onClick={onClear}>
          CLEAR
        </button>
      </div>
    </div>
  )
}

export default ScamForm
Add structural styles to the form

Each component in your team's project has its own CSS file. This keeps styles scoped and easy to find.

  • Create a new file at src/components/ScamForm/ScamForm.css and add the following styles:
.scam-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-label {
  font-size: 0.85rem;
  letter-spacing: 1px;
  font-weight: bold;
}

.form-textarea {
  width: 100%;
  min-height: 150px;
  padding: 14px;
  font-size: 0.95rem;
  resize: vertical;
}

.form-input {
  width: 100%;
  padding: 12px 14px;
  font-size: 0.95rem;
}

.form-buttons {
  display: flex;
  gap: 12px;
  margin-top: 10px;
}

What does this code do?

  • .scam-form uses flexbox in a column direction with a 16px gap between each form element.
  • .form-label makes labels bold and uppercase-looking with letter-spacing.
  • .form-textarea and .form-input set consistent padding and full-width sizing.
  • .form-buttons places the two buttons side by side with a small gap.
  • Save ScamForm.css.

You won't see a change in the browser yet. The next substep connects the component to your app so it actually renders.

Editor still showing an import warning?

If the red squiggly on the import './ScamForm.css' line in ScamForm.jsx disappeared after you saved the CSS file, you are all set. If it persists, make sure ScamForm.css is in the same folder as ScamForm.jsx.

help me fix my CSS import path in ScamForm.jsx

✔️ Awesome, I've got everything!

Great. Make sure you have saved ScamForm.css before moving on.

ⓧ I'd like to double check the full code

.scam-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-label {
  font-size: 0.85rem;
  letter-spacing: 1px;
  font-weight: bold;
}

.form-textarea {
  width: 100%;
  min-height: 150px;
  padding: 14px;
  font-size: 0.95rem;
  resize: vertical;
}

.form-input {
  width: 100%;
  padding: 12px 14px;
  font-size: 0.95rem;
}

.form-buttons {
  display: flex;
  gap: 12px;
  margin-top: 10px;
}
Wire up the Detect page

Components only appear on screen when another file imports and renders them. You need a page component that uses ScamForm, and you need App.jsx to render that page.

  • Create a new file at src/pages/Detect/Detect.jsx and add the following code:
import ScamForm from '../../components/ScamForm/ScamForm.jsx'

function Detect() {
  return (
    <div>
      <h1>EXHIBIT A — SUBMIT MESSAGE</h1>
      <ScamForm
        message=''
        link=''
        onMessageChange={() => {}}
        onLinkChange={() => {}}
        onSubmit={() => {}}
        onClear={() => {}}
      />
    </div>
  )
}

export default Detect

What does this code do?

The Detect page imports ScamForm and renders it with placeholder props. Right now every prop is either an empty string or a no-op function (() => {}).

This is enough to display the form on screen. In the next step, you will replace these placeholders with real state and event handlers.

  • Save Detect.jsx.

✔️ Awesome, I've got everything!

Great. Make sure you have saved Detect.jsx before moving on.

ⓧ I'd like to double check the full code

import ScamForm from '../../components/ScamForm/ScamForm.jsx'

function Detect() {
  return (
    <div>
      <h1>EXHIBIT A — SUBMIT MESSAGE</h1>
      <ScamForm
        message=''
        link=''
        onMessageChange={() => {}}
        onLinkChange={() => {}}
        onSubmit={() => {}}
        onClear={() => {}}
      />
    </div>
  )
}

export default Detect

Now update App.jsx so it renders the Detect page instead of the placeholder text.

  • In src/App.jsx, find this line:
function App() {
  return <div>Scam Detector</div>
}
  • Replace the entire file contents with the following:
import Detect from './pages/Detect/Detect.jsx'

function App() {
  return <Detect />
}

export default App
  • Save App.jsx.
  • Switch to your browser at http://localhost:5173. You should see the heading "EXHIBIT A" and the form with a textarea, a text input, and two buttons ("OPEN THE CASE FILE" and "CLEAR").

Don't see the form in your browser?

  • Check your terminal for red error text. A typo in an import path is the most common cause.
  • Make sure the import in App.jsx points to './pages/Detect/Detect.jsx'. The path is relative to src/.
  • Make sure the import in Detect.jsx points to '../../components/ScamForm/ScamForm.jsx'. Two levels up from pages/Detect/ gets you back to src/.

help me fix my import paths so the ScamForm renders

Your first component is on screen. Next up, you will add real state so typing in the form actually works, and wire up the buttons to submit and clear.

Add State and Interactivity

Your form looks right, but nothing happens when you type or click. The current Detect.jsx passes empty strings and do-nothing functions as props.

React's useState hook lets a component remember values between renders. You'll use it to store the message text, the link text, and the scam analysis result.

In this step, get ready to:
  • Add state variables to Detect.jsx using useState.
  • Connect the form inputs to state so typing updates the page.
  • Write handler functions for submitting and clearing the form.
Add state and handler functions to Detect.jsx

Right now, Detect.jsx passes hardcoded empty strings and arrow functions that do nothing. You'll replace the entire file with a version that uses real state.

  • In VS Code, open src/pages/Detect/Detect.jsx.
  • Select all the existing code and delete it.
  • Replace it with this code:
import { useState } from 'react'
import ScamForm from '../../components/ScamForm/ScamForm.jsx'
import './Detect.css'

function Detect() {
  // State for the message textarea
  const [message, setMessage] = useState('')
  // State for the optional link input
  const [link, setLink] = useState('')
  // State for the analysis result (null means no result yet)
  const [result, setResult] = useState(null)

  function handleSubmit() {
    // Don't submit if the message is empty
    if (message.trim() === '') return
    // Set a mock result object
    setResult({
      verdict: 'High Risk',
      confidence: 92,
      reasons: [
        'Contains urgent language',
        'Requests personal information',
        'Suspicious link detected'
      ]
    })
  }

  function handleClear() {
    // Reset everything back to initial values
    setMessage('')
    setLink('')
    setResult(null)
  }

  return (
    <div className="detect-page">
      <h1 className="detect-title">EXHIBIT A — SUBMIT MESSAGE</h1>
      <ScamForm
        message={message}
        link={link}
        onMessageChange={setMessage}
        onLinkChange={setLink}
        onSubmit={handleSubmit}
        onClear={handleClear}
      />
    </div>
  )
}

export default Detect

What does this code do?

  • useState('') creates a state variable that starts as an empty string. React re-renders the component whenever that value changes.
  • useState(null) creates the result state. It starts as null because there's no analysis yet.
  • handleSubmit checks if the message is empty, then sets a mock result object with a verdict, confidence score, and reasons array.
  • handleClear resets all three state variables back to their initial values.
  • The ScamForm props now pass real state values and setter functions instead of placeholders.

💡 What is useState?

useState is a React hook that gives your component memory.

When you write const [message, setMessage] = useState(''), React gives you two things: the current value (message) and a function to update it (setMessage). Every time you call the setter, React re-renders the component with the new value.

  • Create an empty CSS file for the page by right-clicking the src/pages/Detect/ folder in the Explorer sidebar and selecting New File.
  • Name the file Detect.css and leave it empty for now.
  • Save Detect.jsx.

Seeing an error in the browser?

  • Make sure you created the empty Detect.css file. The import at the top of Detect.jsx will fail if the file doesn't exist.
  • Check that your import path is exactly ../../components/ScamForm/ScamForm.jsx with two levels of ../ to reach the components folder.

Help me fix an import error in my Detect.jsx file.

Trace how data flows between components

You don't need to write any new code here. The goal is to understand how the state in Detect.jsx connects to the form inputs in ScamForm.jsx.

  • Open src/components/ScamForm/ScamForm.jsx and look at the textarea element:
<textarea
  className="form-textarea"
  placeholder="Paste the suspicious message here..."
  value={message}
  onChange={(e) => onMessageChange(e.target.value)}
/>

How does the data flow work?

When the user types, the textarea fires its onChange event. That calls onMessageChange with the new text.

Because Detect passes setMessage as the onMessageChange prop, typing updates the message state in Detect. React re-renders both components with the new value.

This pattern is called lifting state up. The parent component owns the data, and children receive it through props.

Test the submit and clear interactions

The handleSubmit function returns a hardcoded mock result for now. When your team's backend API is ready, you'll replace this mock with a real API call.

  • Switch to your browser at http://localhost:5173.
  • Click into the textarea and type a test message. Your text should appear as you type.
  • Click into the link input and type a test URL.
  • Click CLEAR. Both fields should empty.
  • Type something in the textarea again, then click OPEN THE CASE FILE.

You won't see a result card on the page yet. The result state updates in the background, but you haven't built the component that displays it.

How can I confirm the state updated?

Open your browser's DevTools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows). If you have the React Developer Tools extension installed, go to the Components tab, click on Detect, and you'll see the result state with its verdict, confidence, and reasons.

🙋‍♀️ Typing doesn't update the fields?

  • Make sure you saved Detect.jsx after pasting the new code. Unsaved changes won't reach the browser.
  • Check that the ScamForm props in Detect.jsx pass onMessageChange={setMessage} using curly braces, not quotes.
  • Confirm that ScamForm.jsx still has onChange={(e) => onMessageChange(e.target.value)} on the textarea.

Help me debug why typing in my form doesn't update the displayed text.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete Detect.jsx at the end of this step:

import { useState } from 'react'
import ScamForm from '../../components/ScamForm/ScamForm.jsx'
import './Detect.css'

function Detect() {
  const [message, setMessage] = useState('')
  const [link, setLink] = useState('')
  const [result, setResult] = useState(null)

  function handleSubmit() {
    if (message.trim() === '') return
    setResult({
      verdict: 'High Risk',
      confidence: 92,
      reasons: [
        'Contains urgent language',
        'Requests personal information',
        'Suspicious link detected'
      ]
    })
  }

  function handleClear() {
    setMessage('')
    setLink('')
    setResult(null)
  }

  return (
    <div className="detect-page">
      <h1 className="detect-title">EXHIBIT A — SUBMIT MESSAGE</h1>
      <ScamForm
        message={message}
        link={link}
        onMessageChange={setMessage}
        onLinkChange={setLink}
        onSubmit={handleSubmit}
        onClear={handleClear}
      />
    </div>
  )
}

export default Detect

Your form now captures and resets user input. Next up, you'll build the ResultCard component that displays the scam analysis when the user submits.

Build the ResultCard Component

Your form collects a suspicious message and submits it, but right now the analysis results have nowhere to go. The result state updates behind the scenes, but the user sees nothing change on the page.

In this step, you will build a ResultCard component that displays the scam analysis findings. The Detect page will only show this card after the user submits the form, using conditional rendering to toggle its visibility based on state.

In this step, get ready to:
  • Create the ResultCard component that displays a verdict, confidence score, and evidence list.
  • Style the ResultCard with basic layout CSS.
  • Conditionally render the ResultCard in Detect.jsx so it only appears after form submission.
Create the ResultCard component

The ResultCard needs to read a result object passed in as a prop and display its three fields: the verdict text, the confidence percentage, and an array of evidence reasons.

  • In VS Code, open the src/components/ResultCard/ folder in the Explorer sidebar.
  • Create a new file called ResultCard.jsx inside that folder.
  • Add the following code to ResultCard.jsx:
import './ResultCard.css'

function ResultCard({ result }) {
  return (
    <div className="result-card">
      <h2 className="result-title">CASE FILE RESULTS</h2>
      <div className="result-verdict">
        <span className="verdict-label">Verdict:</span>
        <span className="verdict-value">{result.verdict}</span>
      </div>
      <div className="result-confidence">
        <span className="confidence-label">Confidence:</span>
        <span className="confidence-value">{result.confidence}%</span>
      </div>
      <div className="result-reasons">
        <h3 className="reasons-title">Evidence:</h3>
        <ul className="reasons-list">
          {result.reasons.map((reason, index) => (
            <li key={index}>{reason}</li>
          ))}
        </ul>
      </div>
    </div>
  )
}

export default ResultCard

What does this code do?

This component receives a result prop using destructuring. It reads three properties from that object to display the case file findings.

  • {result.verdict} displays the risk level text (like "High Risk").
  • {result.confidence}% shows the confidence score with a percent sign appended.
  • result.reasons.map() loops through the reasons array and creates a <li> for each one. The key={index} helps React track which list items change.

💡 What is .map()?

.map() is a JavaScript array method that creates a new array by transforming each item. In React, you use it to turn an array of data into an array of JSX elements that render on screen.

  • Save ResultCard.jsx.

You will not see changes in the browser yet because the CSS file is missing and the component is not imported anywhere. The next chunk fixes both.

Add styles and import ResultCard into the Detect page

The component references a CSS file at the top but it does not exist yet. You also need to import ResultCard into your Detect page and tell React when to show it.

  • Create a new file called ResultCard.css inside src/components/ResultCard/.
  • Add the following styles to ResultCard.css:
.result-card {
  margin-top: 30px;
  padding: 24px;
}

.result-title {
  font-size: 1.1rem;
  margin-bottom: 16px;
}

.result-verdict {
  margin-bottom: 10px;
}

.result-confidence {
  margin-bottom: 16px;
}

.reasons-list {
  list-style: none;
  padding: 0;
}

What does this CSS do?

  • .result-card adds spacing above the card so it does not sit flush against the form.
  • .reasons-list removes default bullet points and left padding so the evidence items look clean.
  • The remaining classes add vertical spacing between sections of the card.
  • Save ResultCard.css.
Conditionally render the ResultCard in Detect.jsx

The ResultCard component is ready, but it will not appear on the page until you import it into the Detect page and tell React when to display it. Conditional rendering means showing or hiding a component based on whether a condition is true.

  • Switch back to src/pages/Detect/Detect.jsx.
  • Find the import lines at the top of the file. They currently look like this:
import { useState } from 'react'
import ScamForm from '../../components/ScamForm/ScamForm.jsx'
import './Detect.css'
  • Add a new import line for ResultCard between the ScamForm import and the Detect.css import, so the top of your file looks like this:
import { useState } from 'react'
import ScamForm from '../../components/ScamForm/ScamForm.jsx'
import ResultCard from '../../components/ResultCard/ResultCard.jsx'
import './Detect.css'
  • Now scroll down to the return statement. Find the closing /> of the <ScamForm component.
  • Add this line directly below the closing /> of ScamForm:
      {result && <ResultCard result={result} />}

What does {result && <ResultCard />} do?

This is conditional rendering using the && operator. React reads it as: "If result is not null, render the ResultCard. If it IS null, render nothing."

When the page first loads, result starts as null, so the card stays hidden. After the user clicks "OPEN THE CASE FILE", handleSubmit sets result to the mock object, and the card appears.

  • Save Detect.jsx.
  • Return to your browser at http://localhost:5173.
  • Type any text into the message textarea and click OPEN THE CASE FILE.

You should see the ResultCard appear below the form with "CASE FILE RESULTS" as the heading, "High Risk" as the verdict, "92%" as the confidence, and three evidence bullet points listed underneath.

  • Click CLEAR. The ResultCard should disappear because handleClear sets result back to null.

ResultCard not appearing after clicking submit?

  • Make sure you typed something in the message textarea before clicking submit. The handleSubmit function returns early if the message is empty.
  • Check that the import path in Detect.jsx matches exactly: ../../components/ResultCard/ResultCard.jsx. A wrong path causes a blank screen.
  • Confirm the conditional render line uses && (two ampersands), not a single &.

Help me debug why my ResultCard component is not rendering after I submit the form.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files.

ⓧ I'd like to double check the full code

Here are the complete files you should have at this point.

import './ResultCard.css'

function ResultCard({ result }) {
  return (
    <div className="result-card">
      <h2 className="result-title">CASE FILE RESULTS</h2>
      <div className="result-verdict">
        <span className="verdict-label">Verdict:</span>
        <span className="verdict-value">{result.verdict}</span>
      </div>
      <div className="result-confidence">
        <span className="confidence-label">Confidence:</span>
        <span className="confidence-value">{result.confidence}%</span>
      </div>
      <div className="result-reasons">
        <h3 className="reasons-title">Evidence:</h3>
        <ul className="reasons-list">
          {result.reasons.map((reason, index) => (
            <li key={index}>{reason}</li>
          ))}
        </ul>
      </div>
    </div>
  )
}

export default ResultCard
.result-card {
  margin-top: 30px;
  padding: 24px;
}

.result-title {
  font-size: 1.1rem;
  margin-bottom: 16px;
}

.result-verdict {
  margin-bottom: 10px;
}

.result-confidence {
  margin-bottom: 16px;
}

.reasons-list {
  list-style: none;
  padding: 0;
}
import { useState } from 'react'
import ScamForm from '../../components/ScamForm/ScamForm.jsx'
import ResultCard from '../../components/ResultCard/ResultCard.jsx'
import './Detect.css'

function Detect() {
  const [message, setMessage] = useState('')
  const [link, setLink] = useState('')
  const [result, setResult] = useState(null)

  function handleSubmit() {
    if (message.trim() === '') return
    setResult({
      verdict: 'High Risk',
      confidence: 92,
      reasons: [
        'Contains urgent language',
        'Requests personal information',
        'Suspicious link detected'
      ]
    })
  }

  function handleClear() {
    setMessage('')
    setLink('')
    setResult(null)
  }

  return (
    <div className="detect-page">
      <h1 className="detect-title">EXHIBIT A — SUBMIT MESSAGE</h1>
      <ScamForm
        message={message}
        link={link}
        onMessageChange={setMessage}
        onLinkChange={setLink}
        onSubmit={handleSubmit}
        onClear={handleClear}
      />
      {result && <ResultCard result={result} />}
    </div>
  )
}

export default Detect

Your scam detection form is now fully functional. Users can type a message, submit it, see the case file results, and clear everything to start again. Next up, you will apply the detective theme styling to make the whole page look like the team's design mockup.

Style the Detective Theme

Your components are fully functional, but they still use the browser's default white background and serif font. Your team's design mockup calls for a dark detective theme with gold accents and a monospace typewriter font.

In this step, you'll transform the page from plain HTML into the moody case-file aesthetic your team designed. You'll layer the styles from the outside in: global page styles first, then the page container, then each component.

In this step, get ready to:
  • Add global styles for the dark background, gold text, and monospace font.
  • Style the Detect page container with a case-file card appearance.
  • Theme the form inputs, buttons, and result card with detective-style colors.
Add global styles to index.css

Global styles apply to the entire page regardless of which component is rendering. They set the base font, background color, and layout that everything else sits inside.

  • In VS Code, open src/index.css from the Explorer sidebar.
  • Replace the empty file with the following CSS:
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Courier New', Courier, monospace;
  background-color: #1a1a2e;
  color: #e0d5c1;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 40px 20px;
}

#root {
  width: 100%;
  max-width: 700px;
}

What does this code do?

  • The * reset removes all default browser margins and padding so you start with a clean slate.
  • The body sets the dark navy background, gold-tinted text color, and a monospace typewriter font for the detective mood.
  • Flexbox on body centers the #root container horizontally, and max-width: 700px keeps the content from stretching too wide on large screens.
  • Save src/index.css.
  • Check your browser. The page background should now be a dark navy, and any text should appear in a warm gold color with a monospace font.

Still seeing a white background?

  • Make sure you saved the file. Unsaved changes won't trigger Vite's hot reload.
  • Check that src/main.jsx still has the line import './index.css' near the top. This is what loads your global styles.

Why isn't my dark background showing up even though I saved index.css?

Style the Detect page container

The Detect page needs its own card-like container that sits on top of the dark background. This gives the form a distinct "case file" feel with a slightly lighter background and a gold border.

  • Open src/pages/Detect/Detect.css in VS Code.
  • Replace the empty file with the following CSS:
.detect-page {
  background-color: #2d2b3a;
  border: 2px solid #c9a84c;
  border-radius: 8px;
  padding: 40px;
}

.detect-title {
  font-size: 1.4rem;
  color: #c9a84c;
  text-align: center;
  margin-bottom: 30px;
  letter-spacing: 2px;
}

What does this code do?

  • .detect-page creates the card container with a slightly lighter purple-gray background, a gold border, and rounded corners.
  • .detect-title styles the heading in gold with wide letter-spacing, giving it an official document feel.
  • Save src/pages/Detect/Detect.css.
  • Check your browser. You should see a rounded card with a gold border sitting on the dark background, with "EXHIBIT A" centered in gold at the top.

Don't see the gold border?

  • Confirm that your Detect.jsx file has import './Detect.css' at the top. Without this import, the CSS file is never loaded.
  • Check that the class name detect-page in your CSS matches className="detect-page" in your JSX exactly.

Why isn't my Detect.css styling showing up in the browser?

Theme the form and result card components

The form inputs and buttons still use browser defaults, which clash with the dark theme. You'll replace the existing structural CSS in both component stylesheets with the full detective-themed styles.

  • Open src/components/ScamForm/ScamForm.css.
  • Replace the entire file content with the following CSS:
.scam-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-label {
  font-size: 0.85rem;
  color: #c9a84c;
  letter-spacing: 1px;
  font-weight: bold;
}

.form-textarea {
  width: 100%;
  min-height: 150px;
  padding: 14px;
  background-color: #1a1a2e;
  border: 1px solid #4a4458;
  border-radius: 6px;
  color: #e0d5c1;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.95rem;
  resize: vertical;
}

.form-textarea:focus,
.form-input:focus {
  outline: none;
  border-color: #c9a84c;
}

.form-input {
  width: 100%;
  padding: 12px 14px;
  background-color: #1a1a2e;
  border: 1px solid #4a4458;
  border-radius: 6px;
  color: #e0d5c1;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.95rem;
}

.form-buttons {
  display: flex;
  gap: 12px;
  margin-top: 10px;
}

.btn-submit {
  padding: 12px 24px;
  background-color: #c9a84c;
  color: #1a1a2e;
  border: none;
  border-radius: 6px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.9rem;
  font-weight: bold;
  cursor: pointer;
  letter-spacing: 1px;
}

.btn-submit:hover {
  background-color: #d4b85c;
}

.btn-clear {
  padding: 12px 24px;
  background-color: transparent;
  color: #c9a84c;
  border: 1px solid #c9a84c;
  border-radius: 6px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.9rem;
  font-weight: bold;
  cursor: pointer;
  letter-spacing: 1px;
}

.btn-clear:hover {
  background-color: rgba(201, 168, 76, 0.1);
}

What's happening here?

  • The textarea and input get a dark background with muted borders that light up gold on focus. This gives the user a clear signal of which field is active.
  • .btn-submit uses a solid gold background with dark text, making it the primary action. .btn-clear uses a transparent background with a gold outline, making it visually secondary.
  • Both buttons include hover effects so the user gets feedback when they mouse over them.
  • Save ScamForm.css and check your browser.
  • You should see dark input fields with gold labels. Click inside the textarea to confirm the border turns gold on focus. Hover over the buttons to see their color shift.

Buttons still look like plain HTML buttons?

  • Check that your button elements in ScamForm.jsx have className="btn-submit" and className="btn-clear". Without these class names, the CSS selectors have nothing to target.
  • Make sure you replaced the entire file, not just added code to the end. The old rules and the new rules may conflict.

Help me figure out why my ScamForm button styles aren't applying.

Now apply the same detective treatment to the result card.

  • Open src/components/ResultCard/ResultCard.css.
  • Replace the entire file content with the following CSS:
.result-card {
  margin-top: 30px;
  padding: 24px;
  background-color: #1a1a2e;
  border: 1px solid #c9a84c;
  border-radius: 6px;
}

.result-title {
  font-size: 1.1rem;
  color: #c9a84c;
  margin-bottom: 16px;
  letter-spacing: 1px;
}

.result-verdict {
  margin-bottom: 10px;
}

.verdict-label {
  color: #a09880;
  margin-right: 10px;
}

.verdict-value {
  color: #e74c3c;
  font-weight: bold;
  font-size: 1.1rem;
}

.result-confidence {
  margin-bottom: 16px;
}

.confidence-label {
  color: #a09880;
  margin-right: 10px;
}

.confidence-value {
  color: #e0d5c1;
  font-weight: bold;
}

.reasons-title {
  font-size: 0.9rem;
  color: #c9a84c;
  margin-bottom: 8px;
}

.reasons-list {
  list-style: none;
  padding: 0;
}

.reasons-list li {
  padding: 6px 0;
  color: #e0d5c1;
  border-bottom: 1px solid #4a4458;
}

.reasons-list li:last-child {
  border-bottom: none;
}

What does this code do?

  • The result card gets a dark background with a gold border, making it look like a separate case-file document.
  • .verdict-value uses red (#e74c3c) to make the risk verdict stand out as a warning.
  • The evidence list uses subtle divider lines between items instead of bullet points, keeping the interface clean.
  • Save ResultCard.css.
  • In your browser, type a message and click OPEN THE CASE FILE. The result card should appear with a gold border, a red "High Risk" verdict, and evidence items separated by thin lines.

Verdict text isn't showing in red?

  • Make sure your ResultCard.jsx uses className="verdict-value" on the span wrapping {result.verdict}. The CSS targets that specific class.
  • Check that you replaced the entire file. If old CSS rules remain at the top, they might override the new color styles.

Why isn't my ResultCard showing the correct colors?

✔️ Awesome, I've got everything!

Great. Make sure all four CSS files are saved before moving on.

ⓧ I'd like to double check ScamForm.css

.scam-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-label {
  font-size: 0.85rem;
  color: #c9a84c;
  letter-spacing: 1px;
  font-weight: bold;
}

.form-textarea {
  width: 100%;
  min-height: 150px;
  padding: 14px;
  background-color: #1a1a2e;
  border: 1px solid #4a4458;
  border-radius: 6px;
  color: #e0d5c1;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.95rem;
  resize: vertical;
}

.form-textarea:focus,
.form-input:focus {
  outline: none;
  border-color: #c9a84c;
}

.form-input {
  width: 100%;
  padding: 12px 14px;
  background-color: #1a1a2e;
  border: 1px solid #4a4458;
  border-radius: 6px;
  color: #e0d5c1;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.95rem;
}

.form-buttons {
  display: flex;
  gap: 12px;
  margin-top: 10px;
}

.btn-submit {
  padding: 12px 24px;
  background-color: #c9a84c;
  color: #1a1a2e;
  border: none;
  border-radius: 6px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.9rem;
  font-weight: bold;
  cursor: pointer;
  letter-spacing: 1px;
}

.btn-submit:hover {
  background-color: #d4b85c;
}

.btn-clear {
  padding: 12px 24px;
  background-color: transparent;
  color: #c9a84c;
  border: 1px solid #c9a84c;
  border-radius: 6px;
  font-family: 'Courier New', Courier, monospace;
  font-size: 0.9rem;
  font-weight: bold;
  cursor: pointer;
  letter-spacing: 1px;
}

.btn-clear:hover {
  background-color: rgba(201, 168, 76, 0.1);
}

ⓧ I'd like to double check ResultCard.css

.result-card {
  margin-top: 30px;
  padding: 24px;
  background-color: #1a1a2e;
  border: 1px solid #c9a84c;
  border-radius: 6px;
}

.result-title {
  font-size: 1.1rem;
  color: #c9a84c;
  margin-bottom: 16px;
  letter-spacing: 1px;
}

.result-verdict {
  margin-bottom: 10px;
}

.verdict-label {
  color: #a09880;
  margin-right: 10px;
}

.verdict-value {
  color: #e74c3c;
  font-weight: bold;
  font-size: 1.1rem;
}

.result-confidence {
  margin-bottom: 16px;
}

.confidence-label {
  color: #a09880;
  margin-right: 10px;
}

.confidence-value {
  color: #e0d5c1;
  font-weight: bold;
}

.reasons-title {
  font-size: 0.9rem;
  color: #c9a84c;
  margin-bottom: 8px;
}

.reasons-list {
  list-style: none;
  padding: 0;
}

.reasons-list li {
  padding: 6px 0;
  color: #e0d5c1;
  border-bottom: 1px solid #4a4458;
}

.reasons-list li:last-child {
  border-bottom: none;
}

Your scam detection page is fully styled and ready to show the team. The dark detective theme, gold accents, and typed-letter font all match the design mockup perfectly. If you're up for a bonus challenge, the Secret Mission has you adding clickable sample messages that auto-fill the form.

Secret mission

Add Sample Message Auto-Fill Links

Your form works, but users need to type or paste a message every time they want to test it. What if you could give them clickable sample messages that auto-fill the form instantly? Add lottery scam, bank phish, and ordinary email samples that populate both fields with one click.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files, pause the dev server to free up resources, or delete everything entirely.

Resources you used:

  • The scam-detector project folder on your Desktop.
  • The Vite dev server running at http://localhost:5173.

Keep everything running

No action needed. Choose this if you are still actively building on the project or plan to continue working on it right now.

  • Your Vite dev server stays running at http://localhost:5173.
  • Your scam-detector folder remains on your Desktop.

Pause - I'll come back to this later

Stop the running dev server to free up your terminal and system resources. Your project files stay on disk so you can pick up where you left off.

  • Switch to the terminal in VS Code where the dev server is running.
  • Press Ctrl+C to stop the Vite dev server.
  • To restart later, open the scam-detector folder in VS Code and run npm run dev in the terminal.

Delete - I don't want to use this again

Remove all project files from your machine. This cannot be undone.

  • Switch to the terminal in VS Code where the dev server is running.
  • Press Ctrl+C to stop the Vite dev server.
  • Delete the project folder by running this command (macOS/Linux):
cd ~/Desktop
rm -rf scam-detector

On Windows, run this instead:

cd ~\Desktop
rmdir /s /q scam-detector
  • Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see scam-detector in the output.

Nice Work!

Nice Work!

Nice work! You just built a fully interactive scam detection form page using React components, state management, and conditional rendering.

You've learned how to:

  • Built a multi-component React page with ScamForm, ResultCard, and Detect working together through props.
  • Managed shared state in a parent component using useState and passed data down to child components via props.
  • Used conditional rendering to show the ResultCard only after form submission, and handled user events like typing, submitting, and clearing.
  • Secret Mission: Added sample auto-fill links that demonstrate array rendering with .map() and multi-field state updates from a single click.

Ready to quiz yourself?