diff --git a/src/components/generic/Dialogs/Client/InfoDialog.jsx b/src/components/generic/Dialogs/Client/InfoDialog.jsx
new file mode 100644
index 0000000..dc2dafe
--- /dev/null
+++ b/src/components/generic/Dialogs/Client/InfoDialog.jsx
@@ -0,0 +1,50 @@
+// ─── components/generic/Dialogs/Client/InfoDialog.jsx ────────────────────────
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Info } from "lucide-react";
+
+/**
+ * Generic one-time informational dialog for client-side validation notices —
+ * e.g. "this course has no assessment yet". Purely informational: a single
+ * acknowledgement button, no destructive action.
+ *
+ * Usage:
+ *
+ */
+export function InfoDialog({
+ open,
+ onOpenChange,
+ title,
+ description,
+ icon: Icon = Info,
+ actionLabel = "Continue",
+}) {
+ return (
+
+ );
+}
diff --git a/src/modules/client/pages/AccountSettings.jsx b/src/modules/client/pages/AccountSettings.jsx
index 2b2a063..dff543f 100644
--- a/src/modules/client/pages/AccountSettings.jsx
+++ b/src/modules/client/pages/AccountSettings.jsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
-import { KeyRound, CreditCard, Mail, Megaphone, ChevronRight, Eye, EyeOff, ShieldAlert, Trash2 } from "lucide-react";
+import { KeyRound, CreditCard, Mail, Megaphone, Info, ChevronRight, Eye, EyeOff, ShieldAlert, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@@ -283,6 +283,57 @@ function NewsletterSection() {
);
}
+// ─── Course Notices ───────────────────────────────────────────────────────────
+// One-time informational dialogs shown while studying (e.g. InfoDialog in
+// UnitList.jsx) — this list grows as more generic client notices are added.
+
+const NOTICE_OPTIONS = [
+ {
+ key: "show_course_notices",
+ label: "Course notices",
+ description: "Informational pop-ups about course readiness, such as when an assessment hasn't been built yet.",
+ },
+];
+
+function CourseNoticesSection() {
+ const { profile, getProfile, updateProfile, profileLoading } = useProfile();
+
+ useEffect(() => {
+ getProfile();
+ }, []);
+
+ const handleToggle = async (key, value) => {
+ const result = await updateProfile({ [key]: value });
+ if (result?.success) {
+ toast.success("Preference saved.");
+ }
+ };
+
+ return (
+
+ {NOTICE_OPTIONS.map((opt, i) => (
+
+
+
+
{opt.label}
+
{opt.description}
+
+ {profileLoading ? (
+
+ ) : (
+
handleToggle(opt.key, v)}
+ />
+ )}
+
+ {i < NOTICE_OPTIONS.length - 1 &&
}
+
+ ))}
+
+ );
+}
+
// ─── Advertisements ───────────────────────────────────────────────────────────
const AD_OPTIONS = [
@@ -492,6 +543,10 @@ export default function AccountSettings() {
+
+
diff --git a/src/modules/client/pages/UnitList.jsx b/src/modules/client/pages/UnitList.jsx
index 9db6c1c..710c1d7 100644
--- a/src/modules/client/pages/UnitList.jsx
+++ b/src/modules/client/pages/UnitList.jsx
@@ -12,12 +12,14 @@ import {
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import ResponsiveModal from "@/components/generic/ResponsiveModal";
+import { InfoDialog } from "@/components/generic/Dialogs/Client/InfoDialog";
import LessonBlock from "../components/LessonBlock.jsx";
import QuizBlock from "../components/blocks/QuizBlock.jsx";
import CourseCompleteBlock from "../components/blocks/CourseCompleteBlock.jsx";
import { useClientCourses } from "@/contexts/ClientCoursesContext";
import { PageMeta } from "@/contexts/MetadataContext";
import { useCourseReadingProgress } from "@/contexts/ClientCourseReadingProgressContext";
+import { useProfile } from "@/contexts/ProfileProvider";
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "sonner";
import api from "@/utils/api.util";
@@ -312,6 +314,8 @@ const UnitList = () => {
resetProgress,
} = useCourseReadingProgress();
+ const { profile, getProfile } = useProfile();
+
// Tracks which lessons have been marked completed this session to avoid duplicate calls
const completedSessionRef = useRef(new Set());
@@ -329,6 +333,29 @@ const UnitList = () => {
const resetCompletion = () => setSelectedCompletion(false);
+ // ── No-assessment notice — shown once per course visit, informational only ──
+ // Gated behind the "Course notices" toggle in Account Settings.
+ const [noAssessmentDialogOpen, setNoAssessmentDialogOpen] = useState(false);
+ const noAssessmentNoticeShown = useRef(false);
+
+ useEffect(() => {
+ getProfile();
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ useEffect(() => {
+ noAssessmentNoticeShown.current = false;
+ }, [courseId]);
+
+ useEffect(() => {
+ const shouldShow =
+ course && !courseBlocked && course.duration_seconds &&
+ !course.assessment && !noAssessmentNoticeShown.current &&
+ profile && (profile.personal_info?.show_course_notices ?? true);
+ if (!shouldShow) return;
+ noAssessmentNoticeShown.current = true;
+ setNoAssessmentDialogOpen(true);
+ }, [course, courseBlocked, profile]);
+
// ── Session guard — block navigation while a quiz/assessment is in progress ─
const quizActiveRef = useRef(false); // sync check inside handlers
const [quizSessionActive, setQuizSessionActive] = useState(false);
@@ -780,6 +807,14 @@ const UnitList = () => {
+ {/* ── No-assessment notice — informational, shown once per course visit ── */}
+
+
{/* ── Task-mode banner ─────────────────────────────────────────── */}
{taskCtx?.has_task && (