Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions src/app/courses/[courseId]/modules/[moduleId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { isTextContent, isVideoContent, isInteractiveContent } from "@/types/course";

export default function ModulePage({
params,
Expand Down Expand Up @@ -109,24 +108,6 @@ export default function ModulePage({
<p className="text-gray-500 mb-6 dark:text-gray-400">{module.description}</p>

<div className="prose prose-gray max-w-none dark:prose-invert">
{/* #498 — Render module content with proper type handling */}
{isTextContent(module.content) && (
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(module.content) }} />
)}
{isVideoContent(module.content) && (
<div className="aspect-video rounded-lg overflow-hidden">
<iframe
src={module.content.url}
title={module.title}
className="w-full h-full"
allowFullScreen
/>
</div>
)}
{isInteractiveContent(module.content) && (
<div className="rounded-lg border border-gray-200 p-4">
<p className="text-sm text-gray-500">Interactive content</p>
</div>
{content.type === "video" ? (
<div className="aspect-video w-full bg-black rounded-lg overflow-hidden flex items-center justify-center mb-6">
{content.url ? (
Expand Down
1 change: 0 additions & 1 deletion src/lib/api/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export interface VerifyCredentialResult {
verifiedAt?: string;
error?: VerifyCredentialError;
}
import { getValidToken } from "./auth";

/**
* Fetch all credentials for the authenticated user.
Expand Down
34 changes: 0 additions & 34 deletions src/types/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,18 @@ export interface Course {
createdAt: string;
}

// #498 — Content payload union: text is markdown/HTML string, video is a URL,
// interactive is a config object. This replaces the previous `content: string`
// which forced casting in parseModuleContent.
export type ModuleContent =
| string // text/markdown content
| { type: "video"; url: string; duration?: number }
| { type: "interactive"; config: Record<string, unknown> };


export interface Module {
id: string;
courseId: string;
title: string;
description: string;
order: number;
contentType: "text" | "video" | "interactive";
content: ModuleContent;
content: string | ModuleContent;
estimatedMinutes: number;
isCompleted?: boolean;
}

// #498 — Type guard for Module.content
export function isTextContent(content: ModuleContent): content is string {
return typeof content === "string";
}

export function isVideoContent(
content: ModuleContent
): content is { type: "video"; url: string; duration?: number } {
return typeof content === "object" && content !== null && "type" in content && content.type === "video";
}

export function isInteractiveContent(
content: ModuleContent
): content is { type: "interactive"; config: Record<string, unknown> } {
return typeof content === "object" && content !== null && "type" in content && content.type === "interactive";
}

// #498 — Safe parser that returns string content or empty string
export function parseModuleContent(content: ModuleContent): string {
if (isTextContent(content)) return content;
if (isVideoContent(content)) return content.url;
return "";
}

export interface CourseEnrollment {
id: string;
courseId: string;
Expand Down