Security basics · required by 31 October
Lesson 2: Recognising phishing emails
Step 2 of 5Course progress
Recognising phishing emails
7:20 · Security basics
video-lesson.tsx
"use client";
import { useRef, useState } from "react";
import { LuCircleCheck, LuCirclePlay } from "react-icons/lu";
import { Button, ProgressSteps, VideoPlayer } from "@nexera-ui/react";
const CHAPTERS = [
{ title: "Why phishing works", start: 0, time: "0:00" },
{ title: "Spotting a fake sender", start: 95, time: "1:35" },
{ title: "Links and attachments", start: 210, time: "3:30" },
{ title: "What to do if you clicked", start: 342, time: "5:42" },
];
/** Video lesson: a training video with chapters to jump between and the course progress. */
export default function VideoLesson() {
const videoRef = useRef<HTMLVideoElement>(null);
const [chapter, setChapter] = useState(0);
const [finished, setFinished] = useState(false);
const jump = (index: number) => {
setChapter(index);
const video = videoRef.current;
const start = CHAPTERS[index]?.start;
if (video && start !== undefined) video.currentTime = start;
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-5xl gap-6">
<header className="grid gap-4 @3xl:grid-cols-[1fr_minmax(0,20rem)] @3xl:items-end">
<div className="grid gap-1">
<p className="text-body-small text-secondary">
Security basics · required by 31 October
</p>
<h2 className="text-heading-h3 text-primary">Lesson 2: Recognising phishing emails</h2>
</div>
<ProgressSteps title="Course progress" steps={5} current={finished ? 3 : 2} />
</header>
<div className="grid gap-6 @5xl:grid-cols-[minmax(0,1fr)_20rem]">
{/* Swap the src for your own hosted video; it only loads when someone presses play. */}
<VideoPlayer
title="Recognising phishing emails"
description="7:20 · Security basics"
src="/videos/security-basics-phishing.mp4"
preload="none"
videoRef={videoRef}
onEnded={() => {
setFinished(true);
}}
fallback={
<svg viewBox="0 0 640 360" preserveAspectRatio="xMidYMid slice">
<rect width="640" height="360" className="fill-brand-subtle" />
<rect x="190" y="70" width="260" height="190" rx="16" className="fill-surface" />
<rect
x="190"
y="70"
width="260"
height="44"
rx="16"
className="fill-brand-primary"
/>
<rect x="216" y="140" width="150" height="14" rx="7" className="fill-sunken" />
<rect x="216" y="168" width="200" height="14" rx="7" className="fill-sunken" />
<rect
x="216"
y="196"
width="120"
height="14"
rx="7"
className="fill-status-warning-border"
/>
<circle cx="470" cy="250" r="44" className="fill-status-danger-bg" />
<path
d="M470 226v28m0 14v2"
strokeWidth="10"
strokeLinecap="round"
className="stroke-status-danger-fg"
/>
</svg>
}
/>
<nav
aria-labelledby="chapters-title"
className="grid content-start gap-3 rounded-xl border border-default bg-surface p-4"
>
<h3 id="chapters-title" className="text-heading-h4 text-primary">
Chapters
</h3>
<ol className="grid gap-1">
{CHAPTERS.map((c, i) => (
<li key={c.title}>
<button
type="button"
aria-current={i === chapter ? "step" : undefined}
onClick={() => {
jump(i);
}}
className="flex w-full items-start gap-3 rounded-md px-3 py-2 text-start hover:bg-hover focus-visible:outline-2 aria-[current]:bg-brand-subtle"
>
{i < chapter ? (
<LuCircleCheck
aria-hidden
className="mt-0.5 shrink-0 text-status-success-fg"
/>
) : (
<LuCirclePlay aria-hidden className="mt-0.5 shrink-0 text-muted" />
)}
<span className="grid flex-1">
<span className="text-body-strong text-primary">{c.title}</span>
<span className="text-body-small text-secondary">{c.time}</span>
</span>
</button>
</li>
))}
</ol>
<Button variant="secondary" size="md" isDisabled={!finished} className="w-full">
Next lesson: Safe passwords
</Button>
</nav>
</div>
</div>
</div>
);
}