feat: various improvements

wip: frontend

feat: enable cascade on foreign keys

wip1

wip2

fix chapters issues

wip4
This commit is contained in:
swve 2023-11-29 22:29:48 +01:00
parent 2bf80030d7
commit 187f75e583
71 changed files with 879 additions and 568 deletions

View file

@ -8,7 +8,7 @@ function DocumentPdfActivity({ activity, course }: { activity: any; course: any
<div className="m-8 bg-zinc-900 rounded-md mt-14">
<iframe
className="rounded-lg w-full h-[900px]"
src={getActivityMediaDirectory(activity.org_id, activity.course_id, activity.activity_id, activity.content.documentpdf.filename, 'documentpdf')}
src={getActivityMediaDirectory(activity.org_id, activity.course_uuid, activity.activity_id, activity.content.documentpdf.filename, 'documentpdf')}
/>
</div>
);

View file

@ -38,7 +38,7 @@ function VideoActivity({ activity, course }: { activity: any; course: any }) {
{videoType === 'video' && (
<div className="m-8 bg-zinc-900 rounded-md mt-14">
<video className="rounded-lg w-full h-[500px]" controls
src={getActivityMediaDirectory(activity.org_id, activity.course_id, activity.activity_id, activity.content.video.filename, 'video')}
src={getActivityMediaDirectory(activity.org_id, activity.course_uuid, activity.activity_id, activity.content.video.filename, 'video')}
></video>
</div>

View file

@ -50,8 +50,8 @@ interface Editor {
function Editor(props: Editor) {
const auth: any = React.useContext(AuthContext);
// remove course_ from course_id
const course_id = props.course.course.course_id.substring(7);
// remove course_ from course_uuid
const course_uuid = props.course.course.course_uuid.substring(7);
// remove activity_ from activity_id
const activity_id = props.activity.activity_id.substring(9);
@ -145,8 +145,8 @@ function Editor(props: Editor) {
<Link href="/">
<EditorInfoLearnHouseLogo width={25} height={25} src={learnhouseIcon} alt="" />
</Link>
<Link target="_blank" href={`/course/${course_id}/edit`}>
<EditorInfoThumbnail src={`${getCourseThumbnailMediaDirectory(props.course.course.org_id, props.course.course.course_id, props.course.course.thumbnail)}`} alt=""></EditorInfoThumbnail>
<Link target="_blank" href={`/course/${course_uuid}/edit`}>
<EditorInfoThumbnail src={`${getCourseThumbnailMediaDirectory(props.course.course.org_id, props.course.course.course_uuid, props.course.course.thumbnail)}`} alt=""></EditorInfoThumbnail>
</Link>
<EditorInfoDocName>
{" "}
@ -167,7 +167,7 @@ function Editor(props: Editor) {
<EditorLeftOptionsSection className="space-x-2 pl-2 pr-3">
<div className="bg-sky-600 hover:bg-sky-700 transition-all ease-linear px-3 py-2 font-black text-sm shadow text-teal-100 rounded-lg hover:cursor-pointer" onClick={() => props.setContent(editor.getJSON())}> Save </div>
<ToolTip content="Preview">
<Link target="_blank" href={`/course/${course_id}/activity/${activity_id}`}>
<Link target="_blank" href={`/course/${course_uuid}/activity/${activity_id}`}>
<div className="flex bg-neutral-600 hover:bg-neutral-700 transition-all ease-linear h-9 px-3 py-2 font-black justify-center items-center text-sm shadow text-neutral-100 rounded-lg hover:cursor-pointer">
<Eye className="mx-auto items-center" size={15} />
</div>

View file

@ -70,7 +70,7 @@ function ImageBlockComponent(props: any) {
<img
src={`${getActivityBlockMediaDirectory(props.extension.options.activity.org_id,
props.extension.options.activity.course_id,
props.extension.options.activity.course_uuid,
props.extension.options.activity.activity_id,
blockObject.block_id,
blockObject ? fileId : ' ', 'imageBlock')}`}

View file

@ -50,7 +50,7 @@ function PDFBlockComponent(props: any) {
<iframe
className="shadow rounded-lg h-96 w-full object-scale-down bg-black"
src={`${getActivityBlockMediaDirectory(props.extension.options.activity.org_id,
props.extension.options.activity.course_id,
props.extension.options.activity.course_uuid,
props.extension.options.activity.activity_id,
blockObject.block_id,
blockObject ? fileId : ' ', 'pdfBlock')}`}

View file

@ -51,7 +51,7 @@ function VideoBlockComponents(props: any) {
controls
className="rounded-lg shadow h-96 w-full object-scale-down bg-black"
src={`${getActivityBlockMediaDirectory(props.extension.options.activity.org_id,
props.extension.options.activity.course_id,
props.extension.options.activity.course_uuid,
props.extension.options.activity.activity_id,
blockObject.block_id,
blockObject ? fileId : ' ', 'videoBlock')}`}

View file

@ -9,7 +9,7 @@ import VideoModal from "./NewActivityModal/Video";
import Image from "next/image";
import DocumentPdfModal from "./NewActivityModal/DocumentPdf";
function NewActivityModal({ closeModal, submitActivity, submitFileActivity, submitExternalVideo, chapterId }: any) {
function NewActivityModal({ closeModal, submitActivity, submitFileActivity, submitExternalVideo, chapterId, course }: any) {
const [selectedView, setSelectedView] = useState("home");
@ -39,16 +39,16 @@ function NewActivityModal({ closeModal, submitActivity, submitFileActivity, subm
)}
{selectedView === "dynamic" && (
<DynamicCanvaModal submitActivity={submitActivity} chapterId={chapterId} />
<DynamicCanvaModal submitActivity={submitActivity} chapterId={chapterId} course={course} />
)}
{selectedView === "video" && (
<VideoModal submitFileActivity={submitFileActivity} submitExternalVideo={submitExternalVideo}
chapterId={chapterId} />
chapterId={chapterId} course={course} />
)}
{selectedView === "documentpdf" && (
<DocumentPdfModal submitFileActivity={submitFileActivity} chapterId={chapterId} />
<DocumentPdfModal submitFileActivity={submitFileActivity} chapterId={chapterId} course={course} />
)}
</div>
);

View file

@ -3,7 +3,7 @@ import React, { useState } from "react";
import * as Form from '@radix-ui/react-form';
import BarLoader from "react-spinners/BarLoader";
function DocumentPdfModal({ submitFileActivity, chapterId }: any) {
function DocumentPdfModal({ submitFileActivity, chapterId, course }: any) {
const [documentpdf, setDocumentPdf] = React.useState(null) as any;
const [isSubmitting, setIsSubmitting] = useState(false);
const [name, setName] = React.useState("");
@ -19,7 +19,13 @@ function DocumentPdfModal({ submitFileActivity, chapterId }: any) {
const handleSubmit = async (e: any) => {
e.preventDefault();
setIsSubmitting(true);
let status = await submitFileActivity(documentpdf, "documentpdf", { name, type: "documentpdf" }, chapterId);
let status = await submitFileActivity(documentpdf, "documentpdf", { name: name,
chapter_id: chapterId,
activity_type: "TYPE_DOCUMENT",
activity_sub_type:"SUBTYPE_DOCUMENT_PDF",
published_version:1,
version:1,
course_id: course.id, }, chapterId);
setIsSubmitting(false);
};

View file

@ -3,7 +3,7 @@ import React, { useState } from "react";
import * as Form from '@radix-ui/react-form';
import BarLoader from "react-spinners/BarLoader";
function DynamicCanvaModal({ submitActivity, chapterId }: any) {
function DynamicCanvaModal({ submitActivity, chapterId, course }: any) {
const [activityName, setActivityName] = useState("");
const [activityDescription, setActivityDescription] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
@ -21,9 +21,12 @@ function DynamicCanvaModal({ submitActivity, chapterId }: any) {
setIsSubmitting(true);
await submitActivity({
name: activityName,
chapterId: chapterId,
type: "dynamic",
org_id : "test",
chapter_id: chapterId,
activity_type: "TYPE_DYNAMIC",
activity_sub_type:"SUBTYPE_DYNAMIC_PAGE",
published_version:1,
version:1,
course_id: course.id,
});
setIsSubmitting(false);
};

View file

@ -8,10 +8,11 @@ interface ExternalVideoObject {
name: string,
type: string,
uri: string
chapter_id: string
}
function VideoModal({ submitFileActivity, submitExternalVideo, chapterId }: any) {
function VideoModal({ submitFileActivity, submitExternalVideo, chapterId, course }: any) {
const [video, setVideo] = React.useState(null) as any;
const [isSubmitting, setIsSubmitting] = useState(false);
const [name, setName] = React.useState("");
@ -33,18 +34,30 @@ function VideoModal({ submitFileActivity, submitExternalVideo, chapterId }: any)
const handleSubmit = async (e: any) => {
e.preventDefault();
setIsSubmitting(true);
if (selectedView === "file") {
let status = await submitFileActivity(video, "video", { name, type: "video" }, chapterId);
let status = await submitFileActivity(video, "video", {
name: name,
chapter_id: chapterId,
activity_type: "TYPE_VIDEO",
activity_sub_type: "SUBTYPE_VIDEO_HOSTED",
published_version: 1,
version: 1,
course_id: course.id,
}, chapterId);
setIsSubmitting(false);
}
if (selectedView === "youtube") {
let external_video_object: ExternalVideoObject = {
name,
type: "youtube",
uri: youtubeUrl
uri: youtubeUrl,
chapter_id: chapterId
}
let status = await submitExternalVideo(external_video_object, 'activity' ,chapterId);
let status = await submitExternalVideo(external_video_object, 'activity', chapterId);
setIsSubmitting(false);
}

View file

@ -4,7 +4,7 @@ import * as Form from '@radix-ui/react-form';
import React, { useState } from "react";
import BarLoader from "react-spinners/BarLoader";
function NewChapterModal({ submitChapter, closeModal }: any) {
function NewChapterModal({ submitChapter, closeModal, course }: any) {
const [chapterName, setChapterName] = useState("");
const [chapterDescription, setChapterDescription] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
@ -19,9 +19,17 @@ function NewChapterModal({ submitChapter, closeModal }: any) {
const handleSubmit = async (e: any) => {
e.preventDefault();
setIsSubmitting(true);
await submitChapter({ name: chapterName, description: chapterDescription, activities: [] });
const chapter_object = {
name: chapterName,
description: chapterDescription,
thumbnail_image: "",
course_id: course.id,
org_id: course.org_id
};
await submitChapter(chapter_object);
setIsSubmitting(false);
};
@ -49,9 +57,9 @@ function NewChapterModal({ submitChapter, closeModal }: any) {
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
<Form.Submit asChild>
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
{isSubmitting ? <BarLoader cssOverride={{borderRadius:60,}} width={60} color="#ffffff" />
: "Create Chapter"}
</ButtonBlack>
{isSubmitting ? <BarLoader cssOverride={{ borderRadius: 60, }} width={60} color="#ffffff" />
: "Create Chapter"}
</ButtonBlack>
</Form.Submit>
</Flex>
</FormLayout>

View file

@ -1,3 +1,4 @@
'use client';
import FormLayout, { ButtonBlack, Flex, FormField, FormLabel, Input, Textarea } from '@components/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { FormMessage } from "@radix-ui/react-form";
@ -12,16 +13,21 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [name, setName] = React.useState("");
const [description, setDescription] = React.useState("");
const [learnings, setLearnings] = React.useState("");
const [visibility, setVisibility] = React.useState("");
const [tags, setTags] = React.useState("");
const [isLoading, setIsLoading] = React.useState(false);
const [thumbnail, setThumbnail] = React.useState(null) as any;
const router = useRouter();
const [orgId, setOrgId] = React.useState(null) as any;
const [org, setOrg] = React.useState(null) as any;
const getOrgMetadata = async () => {
const org = await getOrganizationContextInfoWithoutCredentials(orgslug, { revalidate: 360, tags: ['organizations'] });
setOrgId(org.org_id);
setOrgId(org.id);
}
@ -33,6 +39,20 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
setDescription(event.target.value);
};
const handleLearningsChange = (event: React.ChangeEvent<any>) => {
setLearnings(event.target.value);
}
const handleVisibilityChange = (event: React.ChangeEvent<any>) => {
setVisibility(event.target.value);
console.log(event.target.value);
}
const handleTagsChange = (event: React.ChangeEvent<any>) => {
setTags(event.target.value);
}
const handleThumbnailChange = (event: React.ChangeEvent<any>) => {
setThumbnail(event.target.files[0]);
};
@ -40,7 +60,9 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
const handleSubmit = async (e: any) => {
e.preventDefault();
setIsSubmitting(true);
let status = await createNewCourse(orgId, { name, description }, thumbnail);
let status = await createNewCourse(orgId, { name, description, tags, visibility }, thumbnail);
await revalidateTags(['courses'], orgslug);
setIsSubmitting(false);
@ -92,13 +114,25 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
<Input onChange={handleThumbnailChange} type="file" required />
</Form.Control>
</FormField>
<FormField name="course-learnings">
<FormField name="course-tags">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Course keywords</FormLabel>
<FormLabel>Course tags (separated by comma)</FormLabel>
<FormMessage match="valueMissing">Please provide learning elements, separated by comma (,)</FormMessage>
</Flex>
<Form.Control asChild>
<Textarea required />
<Textarea onChange={handleTagsChange} required />
</Form.Control>
</FormField>
<FormField name="course-visibility">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Course Visibility</FormLabel>
<FormMessage match="valueMissing">Please choose cours visibility</FormMessage>
</Flex>
<Form.Control asChild>
<select onChange={handleVisibilityChange} className='border border-gray-300 rounded-md p-2' required>
<option value="true">Public (Available to see on the internet) </option>
<option value="false">Private (Private to users) </option>
</select>
</Form.Control>
</FormField>

View file

@ -27,17 +27,17 @@ function CollectionThumbnail(props: PropsType) {
<div className="flex -space-x-5">
{props.collection.courses.slice(0, 2).map((course: any) => (
<>
<Link href={getUriWithOrg(props.orgslug, "/collection/" + removeCollectionPrefix(props.collection.collection_id))}>
<div className="inset-0 rounded-full shadow-2xl bg-cover w-12 h-8 justify-center ring-indigo-800 ring-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.collection.org_id, course.course_id, course.thumbnail)})` }}>
<Link href={getUriWithOrg(props.orgslug, "/collection/" + removeCollectionPrefix(props.collection.collection_uuid))}>
<div className="inset-0 rounded-full shadow-2xl bg-cover w-12 h-8 justify-center ring-indigo-800 ring-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.collection.org_id, course.course_uuid, course.thumbnail)})` }}>
</div>
</Link>
</>
))}
</div>
<Link href={getUriWithOrg(props.orgslug, "/collection/" + removeCollectionPrefix(props.collection.collection_id))}>
<Link href={getUriWithOrg(props.orgslug, "/collection/" + removeCollectionPrefix(props.collection.collection_uuid))}>
<h1 className="font-bold text-md justify-center">{props.collection.name}</h1>
</Link>
<CollectionAdminEditsArea orgslug={props.orgslug} org_id={props.org_id} collection_id={props.collection.collection_id} collection={props.collection} />
<CollectionAdminEditsArea orgslug={props.orgslug} org_id={props.org_id} collection_uuid={props.collection.collection_uuid} collection={props.collection} />
</div>
</div>
)
@ -54,7 +54,10 @@ const CollectionAdminEditsArea = (props: any) => {
}
return (
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
<AuthenticatedClientElement
action="delete"
ressourceType="collection"
orgId={props.org_id} checkMethod='roles'>
<div className="flex space-x-1 justify-center mx-auto z-20 ">
<ConfirmationModal
confirmationMessage="Are you sure you want to delete this collection?"
@ -66,7 +69,7 @@ const CollectionAdminEditsArea = (props: any) => {
rel="noopener noreferrer">
<X size={10} className="text-rose-200 font-bold" />
</div>}
functionToExecute={() => deleteCollectionUI(props.collection_id)}
functionToExecute={() => deleteCollectionUI(props.collection_uuid)}
status='warning'
></ConfirmationModal>
</div>

View file

@ -15,16 +15,16 @@ type PropsType = {
orgslug: string
}
// function to remove "course_" from the course_id
function removeCoursePrefix(course_id: string) {
return course_id.replace("course_", "");
// function to remove "course_" from the course_uuid
function removeCoursePrefix(course_uuid: string) {
return course_uuid.replace("course_", "");
}
function CourseThumbnail(props: PropsType) {
const router = useRouter();
async function deleteCourses(course_id: any) {
await deleteCourseFromBackend(course_id);
async function deleteCourses(course_uuid: any) {
await deleteCourseFromBackend(course_uuid);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
@ -32,9 +32,9 @@ function CourseThumbnail(props: PropsType) {
return (
<div className='relative'>
<AdminEditsArea course={props.course} orgSlug={props.orgslug} courseId={props.course.course_id} deleteCourses={deleteCourses} />
<Link href={getUriWithOrg(props.orgslug, "/course/" + removeCoursePrefix(props.course.course_id))}>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-xl shadow-xl w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.course.org_id, props.course.course_id, props.course.thumbnail)})` }}>
<AdminEditsArea course={props.course} orgSlug={props.orgslug} courseId={props.course.course_uuid} deleteCourses={deleteCourses} />
<Link href={getUriWithOrg(props.orgslug, "/course/" + removeCoursePrefix(props.course.course_uuid))}>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-xl shadow-xl w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.course.org_id, props.course.course_uuid, props.course.thumbnail)})` }}>
</div>
</Link>
@ -45,7 +45,10 @@ function CourseThumbnail(props: PropsType) {
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
return (
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}>
<AuthenticatedClientElement
action="update"
ressourceType="course"
checkMethod='roles' orgId={props.course.org_id}>
<div className="flex space-x-1 absolute justify-center mx-auto z-20 bottom-14 left-1/2 transform -translate-x-1/2">
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
<div