Skip to content
LMS Portal — platform screenshot

Platform · Level Up Training Center · 2026

LMS Portal

Role-based learning portal for parents, trainers and administrators.

Role-based learning portal for Level Up Training Center: parents follow progress, sessions and invoices; trainers run schedules and grade digital assessments; administrators manage learners, payments and reporting in one place.

My role
Product, architecture and full-stack build
Stack
JavaScript · HTML/CSS · Role-based access · Python build pipeline · In-portal assistant
Microsoft-ecosystem fit

▸ Accessibility: keyboard-first, light and dark themes

▸ Role-based access by design

3

Roles: parent, trainer, admin

1

Place for schedule, progress and payments

[REPLACE]

Active learners

Live demo

Interactive demo

Parent, trainer and administrator views with sample data: schedule, assessments, payments and progress.

LMS Portal — main screen
LMS Portal — mobile

Source · Level Up LMS website (sign-in landing) and the public LMS demo with sample data

01 — Problem

Learner progress, session schedules and payments were tracked across spreadsheets and messaging apps, so families and staff had no single view.

02 — Approach

  1. 01

    One data model behind three role-specific views: parent, trainer and administrator.

  2. 02

    Parents see the next session, skill progress, trainer notes and invoices without contacting the centre.

  3. 03

    Trainers manage the day's schedule, record session notes and grade digital assessments that feed the progress view.

  4. 04

    Administrators track learners, collections and outstanding invoices; an in-portal assistant answers questions from the signed-in user's own data.

03 — Architecture

System architecture

  1. S01

    Parent · Trainer · Admin

  2. S02

    Role-based views

  3. S03

    Schedule + assessments

  4. S04

    Payments

  5. S05

    Progress reports

  6. S06

    Assistant

04 — Engineering notes

Reminders that cannot silently fail

Notifications are written in the same transaction as the change that caused them. A worker then claims due deliveries, and SKIP LOCKED lets several workers run without ever sending the same message twice.

ts
// apps/api/src/notifications/dispatcher.ts (production)
const claimed = await db.execute(sql`
  UPDATE notification_deliveries d
     SET status = 'sending', attempts = d.attempts + 1, updated_at = now()
   WHERE d.id IN (
     SELECT id FROM notification_deliveries
      WHERE status IN ('pending', 'failed') AND next_attempt_at <= now()
      ORDER BY next_attempt_at
      LIMIT ${batch}
      FOR UPDATE SKIP LOCKED)
   RETURNING d.id, d.notification_id, d.channel, d.attempts`);

Failures back off exponentially. After the last attempt a delivery is marked dead and shown in the admin delivery log with a retry button.

Closure you can verify

ts
// apps/api/src/lib/crypto.ts (production)
/** Deterministic JSON (sorted keys) so hashes are reproducible by auditors. */
export function canonicalJson(value: unknown): string {
  if (value === null || typeof value !== 'object') return JSON.stringify(value ?? null);
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
  const entries = Object.entries(value as Record<string, unknown>)
    .filter(([, v]) => v !== undefined)
    .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
  return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
}

Each certificate stores the SHA-256 of that canonical payload, covering owner, verifier, checklist and evidence file hashes. A public endpoint re-hashes it on demand.

LMS Portal — detail screen 1
LMS Portal — detail screen 2
LMS Portal — detail screen 3

Outcome

Families follow progress directly, and administrators manage schedules, assessments and payments in one system.

Next project

Follow-up System

Follow-up System — platform screenshot