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

View file

@ -44,7 +44,7 @@ function Activity(props: any) {
}
return (
<Draggable key={props.activity.id} draggableId={props.activity.id} index={props.index}>
<Draggable key={props.activity.uuid} draggableId={String(props.activity.uuid)} index={props.index}>
{(provided) => (
<div
className="flex flex-row py-2 my-2 rounded-md bg-gray-50 text-gray-500 hover:bg-gray-100 hover:scale-102 hover:shadow space-x-1 w-auto items-center ring-1 ring-inset ring-gray-400/10 shadow-sm transition-all delay-100 duration-75 ease-linear" key={props.activity.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
@ -69,16 +69,16 @@ function Activity(props: any) {
</div>
<div className="flex flex-row space-x-2">
{props.activity.type === "dynamic" && <>
{props.activity.type === "TYPE_DYNAMIC" && <>
<Link
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}/edit`}
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.uuid.replace("activity_", "")}/edit`}
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
rel="noopener noreferrer">
<div className="text-sky-100 font-bold text-xs" >Edit </div>
</Link>
</>}
<Link
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`}
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.uuid.replace("activity_", "")}`}
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md"
rel="noopener noreferrer">
<Eye strokeWidth={2} size={15} className="text-gray-600" />

View file

@ -25,18 +25,16 @@ function Chapter(props: any) {
setSelectedChapter(undefined);
let modifiedChapterCopy = {
name: modifiedChapter.chapterName,
description: '',
activities: props.info.list.chapter.activityIds,
}
await updateChapter(chapterId, modifiedChapterCopy)
await mutate(`${getAPIUrl()}chapters/meta/course_${props.courseid}`)
await mutate(`${getAPIUrl()}chapters/course/${props.course_uuid}/meta`)
await revalidateTags(['courses'], props.orgslug)
router.refresh();
}
}
return (
<Draggable key={props.info.list.chapter.id} draggableId={props.info.list.chapter.id} index={props.index}>
<Draggable key={props.info.list.chapter.uuid} draggableId={String(props.info.list.chapter.uuid)} index={props.index}>
{(provided, snapshot) => (
<ChapterWrapper
{...provided.dragHandleProps}
@ -81,7 +79,7 @@ function Chapter(props: any) {
></ConfirmationModal>
</div>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity">
<Droppable key={props.info.list.chapter.id} droppableId={String(props.info.list.chapter.id)} type="activity">
{(provided) => (
<ActivitiesList {...provided.droppableProps} ref={provided.innerRef}>
<div className="flex flex-col">

View file

@ -7,30 +7,37 @@ import React from 'react'
interface Props {
course: any
orgslug: string
course_id: string
course_uuid: string
current_activity?: any
}
function ActivityIndicators(props: Props) {
const course = props.course
const orgslug = props.orgslug
const courseid = props.course_id.replace("course_", "")
const courseid = props.course_uuid.replace("course_", "")
const done_activity_style = 'bg-teal-600 hover:bg-teal-700'
const black_activity_style = 'bg-black hover:bg-gray-700'
const current_activity_style = 'bg-gray-600 animate-pulse hover:bg-gray-700'
const trail = props.course.trail
function isActivityDone(activity: any) {
if (course.trail.activities_marked_complete && course.trail.activities_marked_complete.includes(activity.id) && course.trail.status == "ongoing") {
return true
const runs = course.trail.runs;
for (let run of runs) {
for (let step of run.steps) {
if (step.activity_id === activity.id && step.complete === true) {
return false;
}
}
}
return false
return false;
}
function isActivityCurrent(activity: any) {
let activityid = activity.id.replace("activity_", "")
if (props.current_activity && props.current_activity == activityid) {
let activity_uuid = activity.activity_uuid.replace("activity_", "")
if (props.current_activity && props.current_activity == activity_uuid) {
return true
}
return false
@ -46,7 +53,6 @@ function ActivityIndicators(props: Props) {
return black_activity_style
}
return (
<div className='grid grid-flow-col justify-stretch space-x-6'>
{course.chapters.map((chapter: any) => {
@ -55,8 +61,8 @@ function ActivityIndicators(props: Props) {
<div className='grid grid-flow-col justify-stretch space-x-2'>
{chapter.activities.map((activity: any) => {
return (
<ToolTip sideOffset={8} slateBlack content={activity.name} key={activity.id}>
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}/activity/${activity.id.replace("activity_", "")}`}>
<ToolTip sideOffset={8} slateBlack content={activity.name} key={activity.activity_uuid}>
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}/activity/${activity.activity_uuid.replace("activity_", "")}`}>
<div className={`h-[7px] w-auto ${getActivityClass(activity)} rounded-lg shadow-md`}></div>
</Link>

View file

@ -13,13 +13,13 @@ interface TrailCourseElementProps {
}
function TrailCourseElement(props: TrailCourseElementProps) {
const courseid = props.course.course_id.replace("course_", "")
const courseid = props.course.course_uuid.replace("course_", "")
const course = props.course
const router = useRouter();
async function quitCourse(course_id: string) {
async function quitCourse(course_uuid: string) {
// Close activity
let activity = await removeCourse(course_id, props.orgslug);
let activity = await removeCourse(course_uuid, props.orgslug);
// Mutate course
await revalidateTags(['courses'], props.orgslug);
router.refresh();
@ -32,7 +32,7 @@ function TrailCourseElement(props: TrailCourseElementProps) {
<div className='trailcoursebox flex p-3 bg-white rounded-xl' style={{ boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}>
<Link href={getUriWithOrg(props.orgslug, "/course/" + courseid)}>
<div className="course_tumbnail inset-0 ring-1 ring-inset ring-black/10 rounded-lg relative h-[50px] w-[72px] bg-cover bg-center" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.course.course_object.org_id, props.course.course_object.course_id, props.course.course_object.thumbnail)})`, boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}></div>
<div className="course_tumbnail inset-0 ring-1 ring-inset ring-black/10 rounded-lg relative h-[50px] w-[72px] bg-cover bg-center" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.course.course_object.org_id, props.course.course_object.course_uuid, props.course.course_object.thumbnail)})`, boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}></div>
</Link>
<div className="course_meta pl-5 flex-grow space-y-1">
<div className="course_top">
@ -46,7 +46,7 @@ function TrailCourseElement(props: TrailCourseElementProps) {
</div>
</div>
<div className="course_actions flex-grow flex flex-row-reverse">
<button onClick={() => quitCourse(course.course_id)} className="bg-red-200 text-red-700 hover:bg-red-300 rounded-full text-xs h-5 px-2 font-bold">Quit Course</button>
<button onClick={() => quitCourse(course.course_uuid)} className="bg-red-200 text-red-700 hover:bg-red-300 rounded-full text-xs h-5 px-2 font-bold">Quit Course</button>
</div>
</div>
</div>

View file

@ -1,45 +1,46 @@
'use client';
import React from "react";
import { AuthContext } from "./AuthProvider";
import useSWR, { mutate } from "swr";
import { getAPIUrl } from "@services/config/config";
import { swrFetcher } from "@services/utils/ts/requests";
interface AuthenticatedClientElementProps {
children: React.ReactNode;
checkMethod: 'authentication' | 'roles';
orgId?: string;
ressourceType?: 'collection' | 'course' | 'activity' | 'user' | 'organization';
action?: 'create' | 'update' | 'delete' | 'read';
}
function generateRessourceId(ressourceType: string) {
// for every type of ressource, we need to generate a ressource id, example for a collection: col_XXXXX
if (ressourceType == 'collection') {
return `collection_xxxx`
}
else if (ressourceType == 'course') {
return `course_xxxx`
}
else if (ressourceType == 'activity') {
return `activity_xxxx`
}
else if (ressourceType == 'user') {
return `user_xxxx`
}
else if (ressourceType == 'organization') {
return `org_xxxx`
}
else if (ressourceType === null) {
return `n/a`
}
}
export const AuthenticatedClientElement = (props: AuthenticatedClientElementProps) => {
const auth: any = React.useContext(AuthContext);
const { data: authorization_status, error: error } = useSWR(props.checkMethod == 'roles' && props.ressourceType ? `${getAPIUrl()}users/authorize/ressource/${generateRessourceId(props.ressourceType)}/action/${props.action}` : null, swrFetcher);
console.log(authorization_status);
// Available roles
const org_roles_values = ["admin", "owner"];
const user_roles_values = ["role_admin"];
function checkRoles() {
const org_id = props.orgId;
const org_roles = auth.userInfo.user_object.orgs;
const user_roles = auth.userInfo.user_object.roles;
const org_role = org_roles.find((org: any) => org.org_id == org_id);
const user_role = user_roles.find((role: any) => role.org_id == org_id);
if (org_role && user_role) {
if (org_roles_values.includes(org_role.org_role) || user_roles_values.includes(user_role.role_id)) {
return true;
}
else {
return false;
}
} else {
return false;
}
}
if ((props.checkMethod == 'authentication' && auth.isAuthenticated) || (auth.isAuthenticated && props.checkMethod == 'roles' && checkRoles())) {
if ((props.checkMethod == 'authentication' && auth.isAuthenticated) || (auth.isAuthenticated && props.checkMethod == 'roles' && authorization_status)) {
return <>{props.children}</>;
}
return <></>;

View file

@ -29,10 +29,10 @@ export const HeaderProfileBox = () => {
)}
{auth.isAuthenticated && (
<AccountArea className="-space-x-2">
<div className="text-xs px-4 text-gray-600 p-1.5 rounded-full bg-gray-50">{auth.userInfo.user_object.full_name}</div>
<div className="text-xs px-4 text-gray-600 p-1.5 rounded-full bg-gray-50">{auth.userInfo.username}</div>
<div className="flex -space-x-2 items-center">
<div className="py-4">
<Avvvatars size={26} value={auth.userInfo.user_object.user_id} style="shape" />
<Avvvatars size={26} value={auth.userInfo.user_uuid} style="shape" />
</div>
<Link className="bg-gray-50 p-1.5 rounded-full" href={"/settings"}><GearIcon fontSize={26} /></Link>
</div>