Skip to main content

Token Approval System

Students consume two kinds of "currency" in the student app — AI Coins (used for AI tutor questions, ~2 coins per query) and Teacher Credits (used to ask a human teacher a doubt). When they run out, they request a top-up. Admins review the queue, then Authorize or Decline. The student's wallet updates on approval; on decline they get the rejection reason.

At a glance

Who can do thisStudents request · Admins & sub-admins with token permission authorize/decline
Where it livesStudent app → /req-coin and /req-credits · Admin panel → /dashboard/school/:id/req-coin (and an inline tab on the student detail page)
Triggers notifications?Yes — on approve and on decline
Related featuresCoins & Credits · AI Tutor · Doubts

How it flows

Status lifecycle

A request stays in pending until an admin acts. There's no automatic timeout.

The student side

/req-coin — request AI Coins

Top of the page is an "AI Access" info card explaining "1 AI query ≈ 2 coins". Below:

  • Quick presets: +100, +250, +500 — tapping one fills the amount field. Default selected preset is 250.
  • Enter coins — manual numeric input with + / - steppers.
  • Purpose — required free-text. Empty purpose blocks submission with a toast.
  • Submit button: "Send Request". On success, navigates back to /dashboard.

Validation:

  • Amount must be > 0.
  • Purpose must be non-empty after trim.

/req-credits — request Teacher Credits

Same shape as /req-coin but for teacher credit, used for asking human teachers questions. Request type teacher_credit.

The student does not see a separate "my requests" page on the admin side. Instead, their pending/approved/rejected history is implicit (a successful approval simply tops up their wallet; a rejection appears as a notification with the reason).

The admin side

Main queue: /dashboard/school/:id/req-coin

Titled Asset Allocation Registry in the UI ("Authorize/Decline" language is used throughout — that's the same as approve/reject).

Columns:

ColumnWhat it shows
InitiatorStudent name + ID
Resource TypeAI Coins (gold tag) or Teacher Credit (green tag)
VolumeThe requested amount, large numeric
ProgressStatus: Reviewing (gold), Authorized (green), Declined (red)
TimelineCreated at — date + time
ActionsAUTHORIZE / DECLINE for pending; eye icon for already-actioned

Filters in the toolbar:

  • Search by student identity (debounced 500ms).
  • Date range with a "Filter Ledger" button to apply, plus a Reset button.
  • Resource Type filter: AI Coins / Teacher Credit / all.
  • Status filter: pending / approved / rejected / cancelled / all.

Rows are clickable — opens the detail modal ("Asset Request Intelligence") showing:

  • Student card with status tag
  • Asset Class card (AI Coins vs Teacher Credit)
  • Allocation Volume card (the amount)
  • "Operational Rationale" (the purpose / estimatedUsage field the student typed)
  • Transaction Pipeline (created-at timestamp; if reviewed, who reviewed it)
  • For pending requests, a footer "Authorize Units" button as a second affordance.

The student-detail tab

When admins drill into a single student (/dashboard/school/:id/students/:studentId), the Token Requests tab shows the same list scoped to that student, with a small badge counting pending requests. The Approve / Reject buttons there work identically to the main queue, and the Reject path uses an inline Popconfirm rather than a centered modal.

Authorize action

  • Single click on AUTHORIZE. No confirm dialog.
  • Toast on success: "Request authorized successfully".
  • Status flips to approved, the row shows the green tag, and actions collapse to a view-only eye icon.

Decline action

  • Click DECLINE → confirm modal: "Are you sure you want to decline this asset request?"
  • On confirm, the request is rejected with a fixed admin-side reason: "Transaction declined by administration". (There is no free-text reason input in the current admin UI; the student just sees that string.)
  • Toast: "Request declined".

Partial approval

The current admin UI does not support partial approvals. The only shipped actions are Authorize (grants the full requested amount) and Decline (grants nothing). A "partial approve" would have to come as a future enhancement.

What the student sees back

  • Approved: wallet balance increases by the requested amount; push + in-app notification fires (see Notifications).
  • Rejected: notification with the admin's reason text. The student's wallet is untouched.
  • Cancelled: status reserved for student-initiated withdrawal of a pending request. The list filter exposes it but no admin action produces this status.

Edge cases & things to test

  • Double-click AUTHORIZE — the button should be disabled / loading after the first click. Verify a request can't be approved twice.
  • Decline then re-request — student declines a request, immediately requests the same amount again. Should produce a second pending row, not edit the first.
  • Missing purpose on the student form — submission should fail client-side with a toast. Confirm.
  • Amount of 0 or negative — submission should fail client-side. Confirm.
  • Filter combinations — type=AI Coins + status=pending + date range last 7 days; verify pagination resets to page 1 when any filter changes.
  • Student detail tab badge — when a request is approved, the pending badge count decreases. Refresh the tab and confirm the badge is in sync with the row statuses.
  • Click the row vs click the action button — clicking a button shouldn't also open the detail modal (event propagation must be stopped).
  • Approved request seen on student detailactions cell should render (em-dash), not buttons.
  • Sub-admin without permission — should not see the /req-coin link in the school sidebar at all.
  • Notification fan-out — verify the student's parent (if any) is notified when the request is approved/declined, depending on the school's notification setting.
  • Wallet sync — approve while the student has the wallet screen open in the student app; the new balance should appear without requiring a manual refresh (Socket.IO update).
  • Search by student ID vs name — the placeholder says "Find requests by student identity"; verify search matches both.