feat: Introduce AI code hinting, enforce single-tenant organization model, and add a Code Lab block component.
This commit is contained in:
@@ -38,6 +38,7 @@ import MemoryBlock from "@/components/blocks/MemoryBlock";
|
||||
import RolePlayingBlock from "@/components/blocks/RolePlayingBlock";
|
||||
import PeerReviewBlock from "@/components/blocks/PeerReviewBlock";
|
||||
import MermaidBlock from "@/components/blocks/MermaidBlock";
|
||||
import CodeLabBlock from "@/components/blocks/CodeLabBlock";
|
||||
import SaveToLibraryModal from "@/components/modals/SaveToLibraryModal";
|
||||
import LibraryPanel from "@/components/LibraryPanel";
|
||||
import Modal from "@/components/Modal";
|
||||
@@ -1106,6 +1107,20 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
||||
onUpdate={(updates) => updateBlock(block.id, updates)}
|
||||
/>
|
||||
)}
|
||||
{block.type === 'code-lab' && (
|
||||
<CodeLabBlock
|
||||
id={block.id}
|
||||
title={block.title}
|
||||
language={block.language}
|
||||
instructions={block.instructions}
|
||||
initial_code={block.initial_code}
|
||||
solution={block.solution}
|
||||
test_cases={block.test_cases}
|
||||
editMode={editMode}
|
||||
lessonId={params.lessonId}
|
||||
onChange={(updates) => updateBlock(block.id, updates)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -1148,6 +1163,7 @@ export default function LessonEditor({ params }: { params: { id: string; lessonI
|
||||
{ type: 'audio-response', icon: '🎤', label: 'Oral Practice', color: 'blue' },
|
||||
{ type: 'memory-match', icon: '🧩', label: 'Logic Game', color: 'indigo' },
|
||||
{ type: 'peer-review', icon: '👥', label: 'Peer Review', color: 'slate' },
|
||||
{ type: 'code-lab', icon: '🧑💻', label: 'Code Lab', color: 'indigo' },
|
||||
{ type: 'mermaid', icon: '📊', label: 'Mermaid Diagram', color: 'indigo' },
|
||||
{ type: 'role-playing', icon: '🎭', label: 'Role-Playing AI', color: 'purple' },
|
||||
].map((item) => (
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
import CourseEditorLayout from "@/components/CourseEditorLayout";
|
||||
import OrganizationSelector from "@/components/OrganizationSelector";
|
||||
|
||||
interface FullModule extends Module {
|
||||
lessons: Lesson[];
|
||||
@@ -35,8 +34,6 @@ export default function CourseEditor({ params }: { params: { id: string } }) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false); // Added saving state
|
||||
const { user } = useAuth();
|
||||
|
||||
@@ -63,19 +60,7 @@ export default function CourseEditor({ params }: { params: { id: string } }) {
|
||||
loadData();
|
||||
}, [params.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadOrgs = async () => {
|
||||
if (user?.role === 'admin' && user?.organization_id === '00000000-0000-0000-0000-000000000001') {
|
||||
try {
|
||||
const orgs = await cmsApi.getOrganizations();
|
||||
setOrganizations(orgs);
|
||||
} catch (err) {
|
||||
console.error("Failed to load organizations", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
loadOrgs();
|
||||
}, [user]);
|
||||
|
||||
|
||||
const handleAddModule = async () => {
|
||||
const title = "";
|
||||
@@ -194,27 +179,19 @@ export default function CourseEditor({ params }: { params: { id: string } }) {
|
||||
|
||||
const handlePublish = async () => {
|
||||
if (!course) return;
|
||||
|
||||
const isSuperAdmin = user?.role === 'admin' && user?.organization_id === '00000000-0000-0000-0000-000000000001';
|
||||
|
||||
if (isSuperAdmin && organizations.length > 0) {
|
||||
setIsOrgModalOpen(true);
|
||||
} else {
|
||||
publishCourse();
|
||||
}
|
||||
publishCourse();
|
||||
};
|
||||
|
||||
const publishCourse = async (targetOrgId?: string) => {
|
||||
const publishCourse = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
await cmsApi.publishCourse(params.id as string, targetOrgId);
|
||||
await cmsApi.publishCourse(params.id as string);
|
||||
alert("Course published successfully!");
|
||||
} catch (err) {
|
||||
console.error("Failed to publish course", err);
|
||||
alert("Failed to publish course.");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
setIsOrgModalOpen(false); // Close modal after publishing attempt
|
||||
}
|
||||
};
|
||||
|
||||
@@ -429,15 +406,7 @@ export default function CourseEditor({ params }: { params: { id: string } }) {
|
||||
</button>
|
||||
</div>
|
||||
</CourseEditorLayout>
|
||||
{/* Organization Selector Modal */}
|
||||
<OrganizationSelector
|
||||
isOpen={isOrgModalOpen}
|
||||
onClose={() => setIsOrgModalOpen(false)}
|
||||
organizations={organizations}
|
||||
title="Publish to Organization"
|
||||
actionLabel="Publish Course"
|
||||
onConfirm={(orgId) => publishCourse(orgId)}
|
||||
/>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user