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 <[email protected]>
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
@@ -1,4 +1,5 @@
<script lang="ts">
import { page } from '$app/state';
import * as UnderlineTabs from '@cio/ui/custom/underline-tabs';
import { t } from '$lib/utils/functions/translations';
@@ -12,7 +13,17 @@
activeTab?: string;
};
let { errors, activeTab = $bindable('design') }: Props = $props();
let { errors }: Props = $props();
let activeTab = $state('design');
// Initialize activeTab from URL parameter if present
$effect(() => {
const tabParam = page.url.searchParams.get('tab');
if (tabParam && (tabParam === 'design' || tabParam === 'settings')) {
activeTab = tabParam;
}
});
</script>
<UpgradeBanner>{$t('upgrade.certificate')}</UpgradeBanner>
@@ -7,7 +7,9 @@
import { courseApi } from '$features/course/api';
import { isFreePlan } from '$lib/utils/store/org';
import { getOrderedNavigableContent } from '$features/course/utils/content';
import type { CourseCertificate } from '$features/course/utils/types';
import { ContentType } from '@cio/utils/constants/content';
import { AttentionHighlight } from '$features/ui';
type Props = {
errors: Record<string, string>;
@@ -70,6 +72,16 @@
function onEmailMessageInput(e: Event) {
updateCertificate({ emailMessage: (e.currentTarget as HTMLTextAreaElement).value || null });
}
function validateCertificateSettings(certificate: CourseCertificate | null | undefined): string[] {
const errors: string[] = [];
if (certificate?.isDownloadable && !certificate?.deadline) {
errors.push('course.certification.deadline_required');
}
return errors;
}
const certificateValidationErrors = $derived(validateCertificateSettings(courseApi.course?.certificate));
</script>
<Field.Group class="w-full max-w-md! px-2">
@@ -85,45 +97,62 @@
}}
disabled={$isFreePlan}
/>
<Field.Label for="certificate-downloadable" class="text-gray-600">
{$t('course.navItem.certificates.allow')}
</Field.Label>
<div class="flex-1">
<Field.Label for="certificate-downloadable" class="text-gray-600">
{$t('course.navItem.certificates.allow')}
</Field.Label>
{#if courseApi.course?.certificate?.isDownloadable && certificateValidationErrors.includes('course.certification.deadline_required')}
<Field.Error class="mt-1">{$t('course.certification.deadline_required')}</Field.Error>
{/if}
</div>
</Field.Field>
<Field.Group>
<Field.Field>
<Field.Label for="cert-deadline">{$t('course.certification.deadline_label')}</Field.Label>
<Input
id="cert-deadline"
type="datetime-local"
class="w-full"
value={isoToDatetimeLocal(courseApi.course?.certificate?.deadline ?? null)}
onchange={onCertDeadlineChange}
disabled={$isFreePlan}
/>
<Field.Description>{$t('course.certification.deadline_helper')}</Field.Description>
{#if errors['certificate.deadline']}
<Field.Error>{errors['certificate.deadline']}</Field.Error>
{/if}
</Field.Field>
<AttentionHighlight id="cert-deadline">
<Field.Field>
<Field.Label for="cert-deadline">
{$t('course.certification.deadline_label')}
{#if courseApi.course?.certificate?.isDownloadable}
<span class="ui:text-red-600">*</span>
{/if}
</Field.Label>
<Input
id="cert-deadline"
type="datetime-local"
class="w-full"
value={isoToDatetimeLocal(courseApi.course?.certificate?.deadline ?? null)}
onchange={onCertDeadlineChange}
disabled={$isFreePlan}
/>
<Field.Description>{$t('course.certification.deadline_helper')}</Field.Description>
{#if errors['certificate.deadline']}
<Field.Error>{errors['certificate.deadline']}</Field.Error>
{/if}
{#if certificateValidationErrors.includes('course.certification.deadline_required')}
<Field.Error>{$t('course.certification.deadline_required')}</Field.Error>
{/if}
</Field.Field>
</AttentionHighlight>
<Field.Field>
<Field.Label for="cert-threshold">{$t('course.certification.threshold_label')}</Field.Label>
<Input
id="cert-threshold"
type="number"
min={0}
max={100}
class="w-full"
value={String(courseApi.course?.certificate?.threshold ?? 100)}
oninput={onThresholdInput}
disabled={$isFreePlan}
/>
<Field.Description>{$t('course.certification.threshold_helper')}</Field.Description>
{#if errors['certificate.threshold']}
<Field.Error>{errors['certificate.threshold']}</Field.Error>
{/if}
</Field.Field>
<AttentionHighlight id="cert-threshold">
<Field.Field>
<Field.Label for="cert-threshold">{$t('course.certification.threshold_label')}</Field.Label>
<Input
id="cert-threshold"
type="number"
min={0}
max={100}
class="w-full"
value={String(courseApi.course?.certificate?.threshold ?? 100)}
oninput={onThresholdInput}
disabled={$isFreePlan}
/>
<Field.Description>{$t('course.certification.threshold_helper')}</Field.Description>
{#if errors['certificate.threshold']}
<Field.Error>{errors['certificate.threshold']}</Field.Error>
{/if}
</Field.Field>
</AttentionHighlight>
<Field.Field>
<Field.Label for="cert-final-exercise">{$t('course.certification.final_exercise_label')}</Field.Label>