chore: ai, public course and other ui improvements
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZExerciseSectionAfterBehavior = z.discriminatedUnion('action', [
|
||||
z.object({ action: z.literal('continue') }),
|
||||
z.object({ action: z.literal('go_to_section'), exerciseSectionId: z.string().uuid() }),
|
||||
z.object({ action: z.literal('submit') })
|
||||
]);
|
||||
|
||||
export const ZExerciseSection = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
title: z.string().min(1, 'Section title is required'),
|
||||
description: z.string().optional().nullable(),
|
||||
order: z.number().int().min(0),
|
||||
colorTheme: z.enum(['blue', 'green', 'amber', 'rose', 'violet', 'slate']).default('blue'),
|
||||
afterBehavior: ZExerciseSectionAfterBehavior.default({ action: 'continue' }),
|
||||
questionIds: z.array(z.union([z.string(), z.number().int()])).optional()
|
||||
});
|
||||
|
||||
export const ZExerciseSectionCreate = ZExerciseSection.omit({ id: true });
|
||||
export const ZExerciseSectionUpdate = ZExerciseSection.partial().required({ id: true });
|
||||
|
||||
export type TExerciseSection = z.infer<typeof ZExerciseSection>;
|
||||
export type TExerciseSectionAfterBehavior = z.infer<typeof ZExerciseSectionAfterBehavior>;
|
||||
@@ -1,8 +1,14 @@
|
||||
import * as z from 'zod';
|
||||
|
||||
import { getStarRatingMaxFromSettings, isValidStarRatingValue } from '@cio/question-types';
|
||||
import {
|
||||
VIDEO_RECORDING_PLATFORM_MAX_DURATION_SECONDS,
|
||||
getStarRatingMaxFromSettings,
|
||||
isValidStarRatingValue
|
||||
} from '@cio/question-types';
|
||||
|
||||
import { QUESTION_TYPE } from '../constants';
|
||||
import { ZSlug } from '../shared/slug';
|
||||
import { ZExerciseSection } from './exercise-section';
|
||||
|
||||
const EXERCISE_QUESTION_TYPE_ID_LITERALS = [
|
||||
z.literal(QUESTION_TYPE.RADIO),
|
||||
@@ -18,7 +24,8 @@ const EXERCISE_QUESTION_TYPE_ID_LITERALS = [
|
||||
z.literal(QUESTION_TYPE.HOTSPOT),
|
||||
z.literal(QUESTION_TYPE.LINK),
|
||||
z.literal(QUESTION_TYPE.WORD_BANK),
|
||||
z.literal(QUESTION_TYPE.STAR)
|
||||
z.literal(QUESTION_TYPE.STAR),
|
||||
z.literal(QUESTION_TYPE.VIDEO_RECORDING)
|
||||
] as const;
|
||||
|
||||
export const ZExerciseQuestionTypeId = z.union(EXERCISE_QUESTION_TYPE_ID_LITERALS);
|
||||
@@ -110,6 +117,22 @@ const QUESTION_VALIDATION_RULES: Record<number, Array<(question: QuestionRuleInp
|
||||
return 'Set a correct star value from 1 up to the maximum number of stars';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
],
|
||||
[QUESTION_TYPE.VIDEO_RECORDING]: [
|
||||
(question) => {
|
||||
const settings = question.settings ?? {};
|
||||
const rawDuration = settings.maxDurationSeconds;
|
||||
const maxDurationSeconds =
|
||||
typeof rawDuration === 'number' ? rawDuration : rawDuration != null ? Number(rawDuration) : NaN;
|
||||
if (!Number.isFinite(maxDurationSeconds) || maxDurationSeconds <= 0) {
|
||||
return 'Set a recording duration greater than 0 seconds';
|
||||
}
|
||||
if (maxDurationSeconds > VIDEO_RECORDING_PLATFORM_MAX_DURATION_SECONDS) {
|
||||
return `Recording duration cannot exceed ${VIDEO_RECORDING_PLATFORM_MAX_DURATION_SECONDS} seconds`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
]
|
||||
@@ -118,6 +141,7 @@ const QUESTION_VALIDATION_RULES: Record<number, Array<(question: QuestionRuleInp
|
||||
// Base schema for exercise update question (without refinement)
|
||||
const ZExerciseUpdateQuestionBase = z.object({
|
||||
id: z.number().int().optional(),
|
||||
exerciseSectionId: z.string().uuid().nullable().optional(),
|
||||
question: z.string().min(1),
|
||||
questionTypeId: ZExerciseQuestionTypeId.optional(),
|
||||
points: z.number().int().min(0).optional(),
|
||||
@@ -183,6 +207,7 @@ export const ZExerciseCreate = z.object({
|
||||
order: z.number().int().optional(),
|
||||
courseId: z.string().min(1),
|
||||
dueBy: z.string().optional(),
|
||||
slug: ZSlug.optional(),
|
||||
questions: z.array(ZExerciseCreateQuestion).optional()
|
||||
});
|
||||
export type TExerciseCreate = z.infer<typeof ZExerciseCreate>;
|
||||
@@ -196,7 +221,10 @@ export const ZExerciseUpdate = z.object({
|
||||
isUnlocked: z.boolean().optional(),
|
||||
dueBy: z.string().optional(), // Changed from iso.datetime() to string to match frontend format
|
||||
allowMultipleAttempts: z.boolean().optional(),
|
||||
questions: z.array(ZExerciseUpdateQuestion).optional()
|
||||
slug: ZSlug.optional(),
|
||||
questions: z.array(ZExerciseUpdateQuestion).optional(),
|
||||
sections: z.array(ZExerciseSection).optional(),
|
||||
sectionDisplayMode: z.enum(['one_question', 'all_questions']).optional()
|
||||
});
|
||||
export type TExerciseUpdate = z.infer<typeof ZExerciseUpdate>;
|
||||
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './exercise';
|
||||
export * from './exercise-section';
|
||||
export * from './video-recording';
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as z from 'zod';
|
||||
import { VIDEO_RECORDING_PLATFORM_MAX_DURATION_SECONDS } from '@cio/question-types';
|
||||
|
||||
export const VIDEO_RECORDING_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'] as const;
|
||||
|
||||
export const ZExerciseVideoRecordingParam = z.object({
|
||||
exerciseId: z.string().min(1),
|
||||
questionId: z.string().regex(/^\d+$/).transform(Number)
|
||||
});
|
||||
|
||||
export const ZExerciseVideoRecordingPlaybackParam = ZExerciseVideoRecordingParam.extend({
|
||||
submissionId: z.string().uuid()
|
||||
});
|
||||
|
||||
export const ZExerciseVideoRecordingUploadInit = z.object({
|
||||
fileName: z.string().min(1),
|
||||
mimeType: z.enum(VIDEO_RECORDING_MIME_TYPES),
|
||||
size: z.number().int().positive()
|
||||
});
|
||||
|
||||
export const ZExerciseVideoRecordingUploadComplete = z.object({
|
||||
assetId: z.string().uuid(),
|
||||
storageKey: z.string().min(1),
|
||||
fileName: z.string().min(1),
|
||||
mimeType: z.enum(VIDEO_RECORDING_MIME_TYPES),
|
||||
size: z.number().int().positive(),
|
||||
durationSeconds: z
|
||||
.number()
|
||||
.positive()
|
||||
.max(VIDEO_RECORDING_PLATFORM_MAX_DURATION_SECONDS + 2),
|
||||
recordedAt: z.string().datetime(),
|
||||
retakeCount: z.number().int().min(0).optional()
|
||||
});
|
||||
|
||||
export type TExerciseVideoRecordingParam = z.infer<typeof ZExerciseVideoRecordingParam>;
|
||||
export type TExerciseVideoRecordingPlaybackParam = z.infer<typeof ZExerciseVideoRecordingPlaybackParam>;
|
||||
export type TExerciseVideoRecordingUploadInit = z.infer<typeof ZExerciseVideoRecordingUploadInit>;
|
||||
export type TExerciseVideoRecordingUploadComplete = z.infer<typeof ZExerciseVideoRecordingUploadComplete>;
|
||||
Reference in New Issue
Block a user