fix: validation of allowing teachers create compliance courses without setting deadline (#778)

* Fixed Validation of allowing teachers create compliance courses without setting deadline

* greptile highlightCertificateSettings decraration suggestion

* greptile multiple error bar showing fix suggestion

* organization race condition

* Delete screenshots/Screenshot 2026-07-22 at 11.52.00 AM.png

* deleted unwanted files

* code review fixes

* Changed AttentionHightlight component to work with just one parameter

created fixed Route Enums, Paths and Sections for

* Kebab-case naming fix

* Changes for code comments

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Victor Jack
2026-08-05 13:28:32 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 05936e788d
commit 60056a6236
16 changed files with 359 additions and 83 deletions
@@ -6,6 +6,7 @@
import { Switch } from '@cio/ui/base/switch';
import * as RadioGroup from '@cio/ui/base/radio-group';
import * as Select from '@cio/ui/base/select';
import * as Dialog from '@cio/ui/base/dialog';
import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
import ArrowUpRightIcon from '@lucide/svelte/icons/arrow-up-right';
import XIcon from '@lucide/svelte/icons/x';
@@ -16,8 +17,10 @@
import { TextareaField } from '@cio/ui/custom/textarea-field';
import { InputField } from '@cio/ui/custom/input-field';
import * as Field from '@cio/ui/base/field';
import { UpgradeBanner, UnsavedChanges, UploadWidget, TextEditor } from '$features/ui';
import { UpgradeBanner, UnsavedChanges, UploadWidget, TextEditor, AttentionHighlight } from '$features/ui';
import { Button } from '@cio/ui/base/button';
import { goAndHighlight } from '$lib/routing/go-and-highlight';
import { ROUTE_NAME, ROUTE_SECTIONS } from '$lib/routing/routes';
import { settings } from '$features/course/utils/settings-store';
import Copy from '@lucide/svelte/icons/copy';
@@ -48,6 +51,7 @@
let isLoading = $state(false);
let isDeleting = $state(false);
let openCertificateDeadlineDialog = $state(false);
let errors: {
title: string | undefined;
description: string | undefined;
@@ -158,6 +162,38 @@
isDeleting = false;
}
function onPublishToggle(checked: boolean) {
if (!checked) {
$settings.isPublished = false;
hasUnsavedChanges = true;
return;
}
const isCourseCompliance = $settings.type === 'COMPLIANCE';
const hasCertificateDeadline = !!courseApi.course?.certificate?.deadline;
if (isCourseCompliance && !hasCertificateDeadline) {
openCertificateDeadlineDialog = true;
return;
}
// Otherwise, publish normally
$settings.isPublished = true;
$settings.allowNewStudents = true;
hasUnsavedChanges = true;
}
async function goToCertificateSettings() {
openCertificateDeadlineDialog = false;
const courseId = courseApi.course?.id;
if (!courseId) return;
goAndHighlight(ROUTE_NAME.COURSE_CERTIFICATE, ROUTE_SECTIONS[ROUTE_NAME.COURSE_CERTIFICATE].CERT_DEADLINE, {
id: courseId,
tab: 'settings'
});
}
export async function handleSave() {
if (!$settings.courseTitle) {
errors.title = $t('snackbar.course_settings.error.title');
@@ -347,17 +383,6 @@
loadCourseTags(courseId);
});
$effect(() => {
const sectionId = $page.url.hash.replace('#', '').trim();
if (!sectionId) {
return;
}
requestAnimationFrame(() => {
document.getElementById(sectionId)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
const selectedTagChips = $derived.by(() => {
const allTags = tagApi.tagGroups.flatMap((group) =>
group.tags.map((tag) => ({
@@ -397,6 +422,25 @@
<DeleteModal onDelete={handleDeleteCourse} bind:open={openDeleteModal} />
<Dialog.Root bind:open={openCertificateDeadlineDialog}>
<Dialog.Content class="max-w-md">
<Dialog.Header>
<Dialog.Title>{$t('course.navItem.settings.certificate_deadline_required')}</Dialog.Title>
<Dialog.Description>
{$t('course.navItem.settings.certificate_deadline_required_description')}
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button variant="outline" onclick={() => (openCertificateDeadlineDialog = false)}>
{$t('cancel')}
</Button>
<Button onclick={goToCertificateSettings}>
{$t('course.navItem.settings.go_to_certificate_settings')}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<Field.Group class="w-full max-w-md! px-2">
<Field.Set>
<Field.Legend>{$t('course.navItem.settings.cover_image')}</Field.Legend>
@@ -848,20 +892,14 @@
<Field.Set id="publish">
<Field.Legend>{$t('course.navItem.settings.publish')}</Field.Legend>
<Field.Description>{$t('course.navItem.settings.determines')}</Field.Description>
<Field.Field orientation="horizontal">
<Switch
id="is-published"
checked={$settings.isPublished}
onCheckedChange={(checked) => {
$settings.isPublished = checked;
if (checked) $settings.allowNewStudents = true;
hasUnsavedChanges = true;
}}
/>
<Label for="publish">
{$settings.isPublished ? $t('course.navItem.settings.published') : $t('course.navItem.settings.unpublished')}
</Label>
</Field.Field>
<AttentionHighlight id="publish">
<Field.Field orientation="horizontal">
<Switch id="is-published" checked={$settings.isPublished} onCheckedChange={onPublishToggle} />
<Label for="publish">
{$settings.isPublished ? $t('course.navItem.settings.published') : $t('course.navItem.settings.unpublished')}
</Label>
</Field.Field>
</AttentionHighlight>
{#if showLockedContentNotice}
<Alert.Root variant={isLiveClassCourse ? 'information' : 'warning'}>