mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -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>
|
||||
|
||||
@@ -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 ${
|
||||
|
||||
Reference in New Issue
Block a user