Front-End Coding Task

Exam System โ€” React

Build a small but complete exam-taking flow in React from a Figma design, backed by a hosted API. A clean, working core beats a pile of half-finished features.

โฑ ~1โ€“2 days ๐Ÿ“ฆ Submit: public GitHub repo โš›๏ธ React + Vite ๐ŸŒ RTL ยท Arabic ยท dark theme
API spec โ†“ Open Figma โ†—

1 What you're building

Three screens. Follow the Figma for layout, spacing, and colors.

#ScreenWhat it doesFigma
1ExamTimed test. One question at a time, four options (single choice), previous / next, live countdown, autosave each answer.Exam โ†—
2ResultTrophy + pass / fail message, final score %, and two tiles: number correct and number wrong.Result โ†—
3Answer ReviewWalk the questions showing the student's answer vs. the correct answer, with the explanation.Review โ†—

2 Requirements

Must-have

  1. Start an attempt via the API and render the questions from the response (don't hardcode them).
  2. One question at a time, with previous / next and an "X ู…ู† Y" position indicator. Match the option styling from Figma, including the selected state.
  3. Track the selected answer per question in state, and autosave each selection to the API.
  4. Countdown timer seeded from the API's timeLeft. When it hits zero, auto-submit.
  5. Submit โ†’ fetch the result โ†’ show the Result screen (score %, pass / fail, correct / wrong counts). Don't compute correctness yourself โ€” read it from the result response.
  6. Answer Review screen driven by the result data: student's choice, correct choice, explanation.
  7. RTL Arabic layout, dark theme, responsive (correct at 375px; usable on desktop).
  8. Loading and error states for the async calls (the API adds a small delay so these show).

Stretch โ€” optional, only after the core is solid

3 Tech constraints

4 Design tokens

TokenValue
Screen background#050F2C
Card / option background#06205B
Card border1px solid #123780
Selected option border / accent#00C8FF (cyan)
Primary gradientlinear-gradient(135deg, #00C8FF, #5285FF)
Correct (green)#05CD99
Wrong (red)#EE5D6C
Muted / meta text#92C9FF
Radiuscards 32px, options ~16px
FontCairo (Google Fonts) โ€” weights 400/500/700/800/900

5 API spec

Base URL https://test-api.ebramatef.pro/api/v1

The API is already running โ€” you don't need to start anything. It's seeded with one real exam (ุงุฎุชุจุงุฑ ุงู„ูƒู‡ุฑุจูŠุฉ โ€” ู†ู…ูˆุฐุฌ A, 8 single-choice questions, 30 minutes, pass mark 50%). Every endpoint adds a small artificial delay so your loading states are visible.

Two things to notice (they're deliberate):
No authentication (assumes a single student). Attempts are held in memory, so it's meant for one candidate at a time, and a restart clears in-progress attempts. Retakes are unlimited โ€” after you submit, starting again begins a fresh attempt.
GET/exams

Lists available exams.

{
  "exams": [
    {
      "id": 2,
      "name": "ุงุฎุชุจุงุฑ ุงู„ูƒู‡ุฑุจูŠุฉ โ€” ู†ู…ูˆุฐุฌ A",
      "time": 30,
      "totalQuestions": 8,
      "resultsMode": "instant",
      "attemptsUsed": 0,
      "maxAttempts": null,
      "attempt": null
    }
  ]
}
POST/exams/:exam/start

Starts (or resumes) an attempt and returns the questions. Before you submit, calling it again resumes the same attempt (refresh-safe); after you submit, it starts a fresh attempt.

{
  "entryId": 1001,
  "examId": 2,
  "name": "ุงุฎุชุจุงุฑ ุงู„ูƒู‡ุฑุจูŠุฉ โ€” ู†ู…ูˆุฐุฌ A",
  "timeLeft": 1800,
  "endsAt": "2026-07-25T12:00:00.000Z",
  "questions": [
    {
      "id": 7,
      "order": 1,
      "type": "choose",
      "title": "ุญุณุงุจ ุงู„ู…ู‚ุงูˆู…ุฉ R",
      "body": "ููŠ ุงู„ุฏุงุฆุฑุฉ ุงู„ู…ู‚ุงุจู„ุฉ ...",
      "equation": null,
      "isLatin": false,
      "mark": 1,
      "maxSelections": 1,
      "images": [],
      "options": [
        { "index": 1, "text": "8"  },
        { "index": 2, "text": "12" },
        { "index": 3, "text": "16" },
        { "index": 4, "text": "24" }
      ],
      "savedAnswer": []
    }
  ]
}
PUT/exam-entries/:entry/answers/:question

Autosave a single answer as the student picks it. Use the stable option index.

// request body
{ "answer": "2" }          // single, or "1,3" / [1,3] for multi-select

// response
{ "saved": true, "answer": [2] }
POST/exam-entries/:entry/submit

Submits the attempt.

{ "status": "submitted", "entryId": 1001, "resultsMode": "instant", "resultsAt": null }
GET/exam-entries/:entry

Returns the graded result (this exam releases instantly). Use it for both the Result and Review screens. While the attempt is still running it returns { "status": "running", "timeLeft": โ€ฆ } instead.

{
  "entryId": 1001,
  "examId": 2,
  "status": "marked",
  "released": true,
  "examMark": 6,
  "totalMark": 8,
  "passMark": 50,
  "passed": true,
  "submittedAt": "2026-07-25T12:05:00.000Z",
  "questions": [
    {
      "id": 7,
      "order": 1,
      "title": "ุญุณุงุจ ุงู„ู…ู‚ุงูˆู…ุฉ R",
      "mark": 1,
      "options": [
        { "index": 1, "text": "8",  "isCorrect": false },
        { "index": 2, "text": "12", "isCorrect": true  },
        { "index": 3, "text": "16", "isCorrect": false },
        { "index": 4, "text": "24", "isCorrect": false }
      ],
      "yourAnswer": [2],
      "questionMark": 1,
      "solutionStatus": "correct",
      "explanation": "ุงู„ุญู„: ุจุชุทุจูŠู‚ ู‚ุงู†ูˆู† ุฃูˆู… ..."
    }
  ]
}

6 What we'll look at

Good luck โ€” we're looking forward to seeing what you build. ๐Ÿš€

Questions about the design or API? Just reach out.