add: more things

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-04 06:48:04 +08:00
parent 8c4e3ef118
commit 536c644623
3 changed files with 141 additions and 1 deletions
+56 -1
View File
@@ -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 (
<div className="space-y-4">
{NOTICE_OPTIONS.map((opt, i) => (
<div key={opt.key}>
<div className="flex items-center justify-between gap-4">
<div className="space-y-0.5">
<p className="text-sm font-medium">{opt.label}</p>
<p className="text-xs text-muted-foreground">{opt.description}</p>
</div>
{profileLoading ? (
<Skeleton className="h-6 w-11 rounded-full shrink-0" />
) : (
<Switch
checked={profile?.personal_info?.[opt.key] ?? true}
onCheckedChange={(v) => handleToggle(opt.key, v)}
/>
)}
</div>
{i < NOTICE_OPTIONS.length - 1 && <Separator className="mt-4" />}
</div>
))}
</div>
);
}
// ─── Advertisements ───────────────────────────────────────────────────────────
const AD_OPTIONS = [
@@ -492,6 +543,10 @@ export default function AccountSettings() {
<NewsletterSection />
</Section>
<Section icon={Info} title="Course Notices" description="Control informational notices shown while studying.">
<CourseNoticesSection />
</Section>
<Section icon={Megaphone} title="Advertisements" description="Control which advertisements you see across the platform.">
<AdvertisementsSection />
</Section>
+35
View File
@@ -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 = () => {
</div>
</ResponsiveModal>
{/* ── No-assessment notice — informational, shown once per course visit ── */}
<InfoDialog
open={noAssessmentDialogOpen}
onOpenChange={setNoAssessmentDialogOpen}
title="Assessment Coming Soon"
description="This course does not have an assessment built yet — we are currently working on it. You may proceed with your studies. Enjoy!"
/>
{/* ── Task-mode banner ─────────────────────────────────────────── */}
{taskCtx?.has_task && (
<div className={`fixed top-[112px] left-0 right-0 z-30 flex items-center gap-2 text-white text-xs font-medium px-4 py-1.5 shadow-sm transition-colors ${