fix: thumbnails

This commit is contained in:
swve 2023-12-13 17:06:51 +01:00
parent c39d9d5340
commit 3413e6ca73
33 changed files with 161 additions and 740 deletions

View file

@ -6,6 +6,7 @@ import { cookies } from "next/headers";
import { Metadata } from "next";
import { getActivityWithAuthHeader } from "@services/courses/activities";
import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth";
import { getOrganizationContextInfo } from "@services/organizations/orgs";
type MetadataProps = {
params: { orgslug: string, courseid: string, activityid: string };
@ -32,6 +33,7 @@ const EditActivity = async (params: any) => {
const activityid = params.params.activityid;
const courseid = params.params.courseid;
const orgslug = params.params.orgslug;
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
const courseInfo = await getCourseMetadataWithAuthHeader(courseid, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null)
const activity = await getActivityWithAuthHeader(activityid, { revalidate: 0, tags: ['activities'] }, access_token ? access_token : null)
@ -40,7 +42,7 @@ const EditActivity = async (params: any) => {
return (
<div>
<AuthProvider>
<EditorWrapper orgslug={orgslug} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>
<EditorWrapper org={org} orgslug={orgslug} course={courseInfo} activity={activity} content={activity.content}></EditorWrapper>
</AuthProvider>
</div>
);

View file

@ -48,6 +48,7 @@ export async function generateMetadata(
const CollectionPage = async (params: any) => {
const cookieStore = cookies();
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
const org = await getOrganizationContextInfo(params.params.orgslug, { revalidate: 1800, tags: ['organizations'] });
const orgslug = params.params.orgslug;
const col = await getCollectionByIdWithAuthHeader(params.params.collectionid, access_token ? access_token : null, { revalidate: 0, tags: ['collections'] });
@ -64,7 +65,7 @@ const CollectionPage = async (params: any) => {
{col.courses.map((course: any) => (
<div className="pr-8" key={course.course_uuid}>
<Link href={getUriWithOrg(orgslug, "/course/" + removeCoursePrefix(course.course_uuid))}>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(course.org_id, course.course_uuid, course.thumbnail)})` }}>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-[249px] h-[131px] bg-cover" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org.org_uuid, course.course_uuid, course.thumbnail_image)})` }}>
</div>
</Link>
<h2 className="font-bold text-lg w-[250px] py-2">{course.name}</h2>

View file

@ -11,6 +11,7 @@ import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWra
import { useRouter } from "next/navigation";
import AuthenticatedClientElement from "@components/Security/AuthenticatedClientElement";
import { getCourseThumbnailMediaDirectory } from "@services/media/media";
import { useOrg } from "@components/Contexts/OrgContext";
interface ActivityClientProps {
activityid: string;
@ -27,6 +28,7 @@ function ActivityClient(props: ActivityClientProps) {
const orgslug = props.orgslug;
const activity = props.activity;
const course = props.course;
const org = useOrg() as any;
function getChapterName(chapterId: string) {
let chapterName = "";
@ -47,7 +49,7 @@ function ActivityClient(props: ActivityClientProps) {
<div className="flex space-x-6">
<div className="flex">
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}`}>
<img className="w-[100px] h-[57px] rounded-md drop-shadow-md" src={`${getCourseThumbnailMediaDirectory(course.course.org_id, course.course.course_uuid, course.course.thumbnail)}`} alt="" />
<img className="w-[100px] h-[57px] rounded-md drop-shadow-md" src={`${getCourseThumbnailMediaDirectory(org?.org_uuid, course.course.course_uuid, course.course.thumbnail_image)}`} alt="" />
</Link>
</div>
<div className="flex flex-col -space-y-1">

View file

@ -12,6 +12,7 @@ import { getCourseThumbnailMediaDirectory } from "@services/media/media";
import { ArrowRight, Check, File, Sparkles, Star, Video } from "lucide-react";
import Avvvatars from "avvvatars-react";
import { getUser } from "@services/users/users";
import { useOrg } from "@components/Contexts/OrgContext";
const CourseClient = (props: any) => {
const [user, setUser] = useState<any>({});
@ -19,6 +20,7 @@ const CourseClient = (props: any) => {
const courseuuid = props.courseuuid;
const orgslug = props.orgslug;
const course = props.course;
const org = useOrg() as any;
const router = useRouter();
function getLearningTags() {
@ -28,7 +30,6 @@ const CourseClient = (props: any) => {
}
console.log(course);
async function startCourseUI() {
// Create activity
@ -57,7 +58,7 @@ const CourseClient = (props: any) => {
useEffect(() => {
}
, []);
, [org]);
return (
<>
@ -73,10 +74,10 @@ const CourseClient = (props: any) => {
</div>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[300px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(course.org_id, course.course_uuid, course.thumbnail)})` }}>
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[300px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, course.course_uuid, course.thumbnail_image)})` }}>
</div>
<ActivityIndicators course_uuid={props.course.course_uuid} orgslug={orgslug} course={course} />
<ActivityIndicators course_uuid={props.course.course_uuid} orgslug={orgslug} course={course} />
<div className="flex flex-row pt-10">
<div className="course_metadata_left grow space-y-2">
@ -198,7 +199,7 @@ const CourseClient = (props: any) => {
}
{console.log(course)}
{isCourseStarted() ? (
{isCourseStarted() ? (
<button className="py-2 px-5 mx-auto rounded-xl text-white font-bold h-12 w-[200px] drop-shadow-md bg-red-600 hover:bg-red-700 hover:cursor-pointer" onClick={quitCourse}>
Quit Course
</button>

View file

@ -1,181 +0,0 @@
"use client";
import React, { FC, useEffect, useReducer } from 'react'
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
import useSWR, { mutate } from 'swr';
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
import Link from 'next/link';
import CourseEdition from '../subpages/CourseEdition';
import CourseContentEdition from '../subpages/CourseContentEdition';
import ErrorUI from '@components/StyledElements/Error/Error';
import { updateChaptersMetadata } from '@services/courses/chapters';
import { Check, SaveAllIcon, Timer } from 'lucide-react';
import Loading from '../../loading';
import { updateCourse } from '@services/courses/courses';
import { useRouter } from 'next/navigation';
function CourseEditClient({ courseuuid, courseid, subpage, params }: { courseid: any, courseuuid: string, subpage: string, params: any }) {
const { data: chapters_meta, error: chapters_meta_error, isLoading: chapters_meta_isloading } = useSWR(`${getAPIUrl()}chapters/course/course_${courseuuid}/meta`, swrFetcher);
const { data: course, error: course_error, isLoading: course_isloading } = useSWR(`${getAPIUrl()}courses/course_${courseuuid}/meta`, swrFetcher);
const [courseChaptersMetadata, dispatchCourseChaptersMetadata] = useReducer(courseChaptersReducer, {});
const [courseState, dispatchCourseMetadata] = useReducer(courseReducer, {});
const [savedContent, dispatchSavedContent] = useReducer(savedContentReducer, true);
const router = useRouter();
// This function is a quick fix to transform the payload object from what was used before to the new and improved format
// The entire course edition frontend code will be remade in the future in a proper way.
const ConvertToNewAPIOrderUpdatePayload = (courseChaptersMetadata: any) => {
const old_format = courseChaptersMetadata
console.log()
// Convert originalObject to the desired format
const convertedObject = {
"chapter_order_by_ids": old_format.chapterOrder.map((chapterId: string | number, chapterIndex: any) => {
const chapter = old_format.chapters[chapterId];
return {
"chapter_id": chapter.id,
"activities_order_by_ids": chapter.activityIds.map((activityId: any, activityIndex: any) => {
return {
"activity_id": activityIndex
};
})
};
})
};
return convertedObject
}
function courseChaptersReducer(state: any, action: any) {
switch (action.type) {
case 'updated_chapter':
// action will contain the entire state, just update the entire state
return action.payload;
default:
throw new Error();
}
}
function courseReducer(state: any, action: any) {
switch (action.type) {
case 'updated_course':
// action will contain the entire state, just update the entire state
return action.payload;
default:
throw new Error();
}
}
function savedContentReducer(state: any, action: any) {
switch (action.type) {
case 'saved_content':
return true;
case 'unsaved_content':
return false;
default:
throw new Error();
}
}
async function saveCourse() {
if (subpage.toString() === 'content') {
let payload = ConvertToNewAPIOrderUpdatePayload(courseChaptersMetadata)
await updateChaptersMetadata(courseuuid, payload)
dispatchSavedContent({ type: 'saved_content' })
await mutate(`${getAPIUrl()}chapters/course/course_${courseuuid}/meta`)
await revalidateTags(['courses'], params.params.orgslug)
router.refresh()
}
else if (subpage.toString() === 'general') {
await updateCourse(courseuuid, courseState)
dispatchSavedContent({ type: 'saved_content' })
await mutate(`${getAPIUrl()}courses/course_${courseuuid}`)
await mutate(`${getAPIUrl()}chapters/course/course_${courseuuid}/meta`)
await revalidateTags(['courses'], params.params.orgslug)
router.refresh()
}
}
useEffect(() => {
if (chapters_meta) {
dispatchCourseChaptersMetadata({ type: 'updated_chapter', payload: chapters_meta })
dispatchSavedContent({ type: 'saved_content' })
}
if (course) {
dispatchCourseMetadata({ type: 'updated_course', payload: course })
dispatchSavedContent({ type: 'saved_content' })
}
}, [chapters_meta, course])
return (
<>
<div className='bg-white shadow-[0px_4px_16px_rgba(0,0,0,0.02)]'>
<div className='max-w-screen-2xl mx-auto px-16 pt-5 tracking-tight'>
{course_isloading && <div className='text-sm text-gray-500'>Loading...</div>}
{course && <>
<div className='flex items-center'><div className='info flex space-x-5 items-center grow'>
<div className='flex'>
<Link href={getUriWithOrg(params.params.orgslug, "") + `/course/${courseuuid}`}>
<img className="w-[100px] h-[57px] rounded-md drop-shadow-md" src={`${getCourseThumbnailMediaDirectory(course.org_id, "course_" + courseuuid, course.thumbnail)}`} alt="" />
</Link>
</div>
<div className="flex flex-col ">
<div className='text-sm text-gray-500'>Edit Course</div>
<div className='text-2xl font-bold first-letter:uppercase'>{course.name}</div>
</div>
</div>
<div className='flex space-x-5 items-center'>
{savedContent ? <></> : <div className='text-gray-600 flex space-x-2 items-center antialiased'>
<Timer size={15} />
<div>
Unsaved changes
</div>
</div>}
<div className={`' px-4 py-2 rounded-lg drop-shadow-md cursor-pointer flex space-x-2 items-center font-bold antialiased transition-all ease-linear ` + (savedContent ? 'bg-gray-600 text-white' : 'bg-black text-white border hover:bg-gray-900 ')
} onClick={saveCourse}>
{savedContent ? <Check size={20} /> : <SaveAllIcon size={20} />}
{savedContent ? <div className=''>Saved</div> : <div className=''>Save</div>}
</div>
</div>
</div>
</>}
<div className='flex space-x-5 pt-3 font-black text-sm'>
<Link href={getUriWithOrg(params.params.orgslug, "") + `/course/${courseuuid}/edit/general`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${subpage.toString() === 'general' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>General</div>
</Link>
<Link href={getUriWithOrg(params.params.orgslug, "") + `/course/${courseuuid}/edit/content`}>
<div className={`py-2 w-16 text-center border-black transition-all ease-linear ${subpage.toString() === 'content' ? 'border-b-4' : 'opacity-50'} cursor-pointer`}>Content</div>
</Link>
</div>
</div>
</div>
<CoursePageViewer course={course} dispatchSavedContent={dispatchSavedContent} courseState={courseState} courseChaptersMetadata={courseChaptersMetadata} dispatchCourseMetadata={dispatchCourseMetadata} dispatchCourseChaptersMetadata={dispatchCourseChaptersMetadata} subpage={subpage} courseuuid={courseuuid} orgslug={params.params.orgslug} />
</>
)
}
const CoursePageViewer = ({ subpage, course, orgslug, dispatchCourseMetadata, dispatchCourseChaptersMetadata, courseChaptersMetadata, dispatchSavedContent, courseState }: { subpage: string, courseuuid: string, orgslug: string, dispatchCourseChaptersMetadata: React.Dispatch<any>, dispatchCourseMetadata: React.Dispatch<any>, dispatchSavedContent: React.Dispatch<any>, courseChaptersMetadata: any, courseState: any, course: any }) => {
if (subpage.toString() === 'general' && Object.keys(courseState).length !== 0 && course) {
return <CourseEdition course={course} orgslug={orgslug} course_chapters_with_orders_and_activities={courseState} dispatchCourseMetadata={dispatchCourseMetadata} dispatchSavedContent={dispatchSavedContent} />
}
else if (subpage.toString() === 'content' && Object.keys(courseChaptersMetadata).length !== 0 && course) {
return <CourseContentEdition course={course} orgslug={orgslug} course_chapters_with_orders_and_activities={courseChaptersMetadata} dispatchSavedContent={dispatchSavedContent} dispatchCourseChaptersMetadata={dispatchCourseChaptersMetadata} />
}
else if (subpage.toString() === 'content' || subpage.toString() === 'general') {
return <Loading />
}
else {
return <ErrorUI />
}
}
export default CourseEditClient

View file

@ -1,44 +0,0 @@
import { getOrganizationContextInfo } from "@services/organizations/orgs";
import CourseEditClient from "./edit";
import { getCourseMetadataWithAuthHeader } from "@services/courses/courses";
import { cookies } from "next/headers";
import { Metadata } from 'next';
import { getAccessTokenFromRefreshTokenCookie, getNewAccessTokenUsingRefreshTokenServer } from "@services/auth/auth";
type MetadataProps = {
params: { orgslug: string, courseuuid: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: MetadataProps,
): Promise<Metadata> {
const cookieStore = cookies();
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
// Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
const course_meta = await getCourseMetadataWithAuthHeader(params.courseuuid, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null)
return {
title: `Edit Course - ` + course_meta.name,
description: course_meta.mini_description,
};
}
async function CourseEdit(params: any) {
const cookieStore = cookies();
const access_token = await getAccessTokenFromRefreshTokenCookie(cookieStore)
let subpage = params.params.subpage ? params.params.subpage : 'general';
const course_meta = await getCourseMetadataWithAuthHeader(params.params.courseuuid, { revalidate: 0, tags: ['courses'] }, access_token ? access_token : null)
return (
<>
<CourseEditClient params={params} subpage={subpage} courseid={course_meta.id} courseuuid={params.params.courseuuid} />
</>
);
}
export default CourseEdit;

View file

@ -1,320 +0,0 @@
"use client";
import React from "react";
import { useState, useEffect } from "react";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import Chapter from "@components/Pages/CourseEdit/Draggables/Chapter";
import { createChapter, deleteChapter, getCourseChaptersMetadata, updateChaptersMetadata } from "@services/courses/chapters";
import { useRouter } from "next/navigation";
import NewChapterModal from "@components/Objects/Modals/Chapters/NewChapter";
import NewActivityModal from "@components/Objects/Modals/Activities/Create/NewActivity";
import { createActivity, createFileActivity, createExternalVideoActivity } from "@services/courses/activities";
import { getOrganizationContextInfo, getOrganizationContextInfoWithoutCredentials } from "@services/organizations/orgs";
import Modal from "@components/StyledElements/Modal/Modal";
import { denyAccessToUser } from "@services/utils/react/middlewares/views";
import { Folders, Hexagon, SaveIcon } from "lucide-react";
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
import { getAPIUrl } from "@services/config/config";
import { mutate } from "swr";
function CourseContentEdition(props: any) {
const router = useRouter();
// Initial Course Chapters State
const course_chapters_with_orders_and_activities = props.course_chapters_with_orders_and_activities;
// New Chapter Modal State
const [newChapterModal, setNewChapterModal] = useState(false) as any;
// New Activity Modal State
const [newActivityModal, setNewActivityModal] = useState(false) as any;
const [selectedChapterToAddActivityTo, setSelectedChapterToAddActivityTo] = useState("") as any;
// Check window availability
const [winReady, setwinReady] = useState(false);
const course = props.course;
const course_uuid = props.course ? props.course.course_uuid : ''
const orgslug = props.orgslug;
//
useEffect(() => {
setwinReady(true);
}, [course_uuid, orgslug]);
// get a list of chapters order by chapter order
const getChapters = () => {
const chapterOrder = course_chapters_with_orders_and_activities.chapterOrder ? course_chapters_with_orders_and_activities.chapterOrder : [];
return chapterOrder.map((chapterId: any) => {
const chapter = course_chapters_with_orders_and_activities.chapters[chapterId];
let activities = [];
if (course_chapters_with_orders_and_activities.activities) {
activities = chapter.activityIds.map((activityId: any) => course_chapters_with_orders_and_activities.activities[activityId])
? chapter.activityIds.map((activityId: any) => course_chapters_with_orders_and_activities.activities[activityId])
: [];
}
return {
list: {
chapter: chapter,
activities: activities,
},
};
});
};
// Submit new chapter
const submitChapter = async (chapter: any) => {
await createChapter(chapter);
mutate(`${getAPIUrl()}chapters/course/${course_uuid}/meta`,true);
await revalidateTags(['courses'], orgslug);
router.refresh();
setNewChapterModal(false);
};
// Submit new activity
const submitActivity = async (activity: any) => {
let org = await getOrganizationContextInfoWithoutCredentials(orgslug, { revalidate: 1800 });
await createActivity(activity, activity.chapterId, org.org_id);
mutate(`${getAPIUrl()}chapters/course/${course_uuid}/meta`);
// await getCourseChapters();
setNewActivityModal(false);
await revalidateTags(['courses'], orgslug);
router.refresh();
};
// Submit File Upload
const submitFileActivity = async (file: any, type: any, activity: any, chapterId: string) => {
//await updateChaptersMetadata(course_uuid, course_chapters_with_orders_and_activities);
await createFileActivity(file, type, activity, chapterId);
mutate(`${getAPIUrl()}chapters/course/${course_uuid}/meta`);
// await getCourseChapters();
setNewActivityModal(false);
await revalidateTags(['courses'], orgslug);
router.refresh();
};
// Submit YouTube Video Upload
const submitExternalVideo = async (external_video_data: any, activity: any, chapterId: string) => {
//await updateChaptersMetadata(course_uuid, course_chapters_with_orders_and_activities);
await createExternalVideoActivity(external_video_data, activity, chapterId);
mutate(`${getAPIUrl()}chapters/course/${course_uuid}/meta`);
// await getCourseChapters();
setNewActivityModal(false);
await revalidateTags(['courses'], orgslug);
router.refresh();
};
const deleteChapterUI = async (chapterId: any) => {
await deleteChapter(chapterId);
mutate(`${getAPIUrl()}chapters/course/${course_uuid}/meta`,true);
// await getCourseChapters();
await revalidateTags(['courses'], orgslug);
router.refresh();
};
/*
Modals
*/
const openNewActivityModal = async (chapterId: any) => {
setNewActivityModal(true);
setSelectedChapterToAddActivityTo(chapterId);
};
// Close new chapter modal
const closeNewChapterModal = () => {
setNewChapterModal(false);
};
const closeNewActivityModal = () => {
setNewActivityModal(false);
};
/*
Drag and drop functions
*/
const onDragEnd = async (result: any) => {
const { destination, source, draggableId, type } = result;
// check if the activity is dropped outside the droppable area
if (!destination) {
return;
}
// check if the activity is dropped in the same place
if (destination.droppableId === source.droppableId && destination.index === source.index) {
return;
}
//////////////////////////// CHAPTERS ////////////////////////////
if (type === "chapter") {
const newChapterOrder = Array.from(course_chapters_with_orders_and_activities.chapterOrder);
newChapterOrder.splice(source.index, 1);
newChapterOrder.splice(destination.index, 0, draggableId);
const newState = {
...course_chapters_with_orders_and_activities,
chapterOrder: newChapterOrder,
};
props.dispatchCourseChaptersMetadata({ type: 'updated_chapter', payload: newState })
props.dispatchSavedContent({ type: 'unsaved_content' })
//setData(newState);
return;
}
//////////////////////// ACTIVITIES IN SAME CHAPTERS ////////////////////////////
// check if the activity is dropped in the same chapter
const start = course_chapters_with_orders_and_activities.chapters[source.droppableId];
const finish = course_chapters_with_orders_and_activities.chapters[destination.droppableId];
// check if the activity is dropped in the same chapter
if (start === finish) {
// create new arrays for chapters and activities
const chapter = course_chapters_with_orders_and_activities.chapters[source.droppableId];
const newActivityIds = Array.from(chapter.activityIds);
// remove the activity from the old position
newActivityIds.splice(source.index, 1);
// add the activity to the new position
newActivityIds.splice(destination.index, 0, draggableId);
const newChapter = {
...chapter,
activityIds: newActivityIds,
};
const newState = {
...course_chapters_with_orders_and_activities,
chapters: {
...course_chapters_with_orders_and_activities.chapters,
[newChapter.id]: newChapter,
},
};
props.dispatchCourseChaptersMetadata({ type: 'updated_chapter', payload: newState })
props.dispatchSavedContent({ type: 'unsaved_content' })
//setData(newState);
return;
}
//////////////////////// ACTIVITIES IN DIFF CHAPTERS ////////////////////////////
// check if the activity is dropped in a different chapter
if (start !== finish) {
// create new arrays for chapters and activities
const startChapterActivityIds = Array.from(start.activityIds);
// remove the activity from the old position
startChapterActivityIds.splice(source.index, 1);
const newStart = {
...start,
activityIds: startChapterActivityIds,
};
// add the activity to the new position within the chapter
const finishChapterActivityIds = Array.from(finish.activityIds);
finishChapterActivityIds.splice(destination.index, 0, draggableId);
const newFinish = {
...finish,
activityIds: finishChapterActivityIds,
};
const newState = {
...course_chapters_with_orders_and_activities,
chapters: {
...course_chapters_with_orders_and_activities.chapters,
[newStart.id]: newStart,
[newFinish.id]: newFinish,
},
};
props.dispatchCourseChaptersMetadata({ type: 'updated_chapter', payload: newState })
props.dispatchSavedContent({ type: 'unsaved_content' })
//setData(newState);
return;
}
};
return (
<>
<div className=""
>
<GeneralWrapperStyled>
<Modal
isDialogOpen={newActivityModal}
onOpenChange={setNewActivityModal}
minHeight="no-min"
addDefCloseButton={false}
dialogContent={<NewActivityModal
closeModal={closeNewActivityModal}
submitFileActivity={submitFileActivity}
submitExternalVideo={submitExternalVideo}
submitActivity={submitActivity}
chapterId={selectedChapterToAddActivityTo}
course={course}
></NewActivityModal>}
dialogTitle="Create Activity"
dialogDescription="Choose between types of activities to add to the course"
/>
{winReady && (
<div className="flex flex-col">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable key="chapters" droppableId="chapters" type="chapter">
{(provided) => (
<>
<div key={"chapters"} {...provided.droppableProps} ref={provided.innerRef}>
{getChapters().map((info: any, index: any) => (
<>
<Chapter
orgslug={orgslug}
course_uuid={course_uuid}
openNewActivityModal={openNewActivityModal}
deleteChapter={deleteChapterUI}
key={index}
info={info}
index={index}
></Chapter>
</>
))}
{provided.placeholder}
</div>
</>
)}
</Droppable>
</DragDropContext>
<Modal
isDialogOpen={newChapterModal}
onOpenChange={setNewChapterModal}
minHeight="sm"
dialogContent={<NewChapterModal
course={props.course ? props.course : null}
closeModal={closeNewChapterModal}
submitChapter={submitChapter}
></NewChapterModal>}
dialogTitle="Create chapter"
dialogDescription="Add a new chapter to the course"
dialogTrigger={
<div className="flex max-w-7xl bg-black text-sm shadow rounded-md items-center text-white justify-center mx-auto space-x-2 p-3 w-72 hover:bg-gray-900 hover:cursor-pointer">
<Hexagon size={16} />
<div>Add chapter +</div>
</div>
}
/>
</div>
)}
</GeneralWrapperStyled >
</div>
</>
);
}
export default CourseContentEdition;

View file

@ -1,110 +0,0 @@
"use client";
import FormLayout, { ButtonBlack, FormField, FormLabel, FormLabelAndMessage, FormMessage, Input, Textarea } from '@components/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form';
import { useFormik } from 'formik';
import { AlertTriangle } from "lucide-react";
import React from "react";
const validate = (values: any) => {
const errors: any = {};
if (!values.name) {
errors.name = 'Required';
}
if (values.name.length > 100) {
errors.name = 'Must be 80 characters or less';
}
if (!values.mini_description) {
errors.mini_description = 'Required';
}
if (values.mini_description.length > 200) {
errors.mini_description = 'Must be 200 characters or less';
}
if (!values.description) {
errors.description = 'Required';
}
if (values.description.length > 1000) {
errors.description = 'Must be 1000 characters or less';
}
if (!values.learnings) {
errors.learnings = 'Required';
}
return errors;
};
function CourseEdition(props: any) {
const [error, setError] = React.useState('');
const formik = useFormik({
initialValues: {
name: String(props.course_chapters_with_orders_and_activities.name),
mini_description: String(props.course_chapters_with_orders_and_activities.mini_description),
description: String(props.course_chapters_with_orders_and_activities.description),
learnings: String(props.course_chapters_with_orders_and_activities.learnings),
},
validate,
onSubmit: async values => {
},
});
React.useEffect(() => {
// This code will run whenever form values are updated
if (formik.values !== formik.initialValues) {
props.dispatchSavedContent({ type: 'unsaved_content' });
const updatedCourse = {
...props.course_chapters_with_orders_and_activities,
name: formik.values.name,
description: formik.values.description,
learnings: formik.values.learnings,
};
props.dispatchCourseMetadata({ type: 'updated_course', payload: updatedCourse });
}
}, [formik.values, formik.initialValues]);
return (
<div className='max-w-screen-2xl mx-auto px-16 pt-5 tracking-tight'>
<div className="login-form">
{error && (
<div className="flex justify-center bg-red-200 rounded-md text-red-950 space-x-2 items-center p-4 transition-all shadow-sm">
<AlertTriangle size={18} />
<div className="font-bold text-sm">{error}</div>
</div>
)}
<FormLayout onSubmit={formik.handleSubmit}>
<FormField name="name">
<FormLabelAndMessage label='Name' message={formik.errors.name} />
<Form.Control asChild>
<Input style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.name} type="text" required />
</Form.Control>
</FormField>
<FormField name="description">
<FormLabelAndMessage label='Description' message={formik.errors.description} />
<Form.Control asChild>
<Textarea style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.description} required />
</Form.Control>
</FormField>
<FormField name="learnings">
<FormLabelAndMessage label='Learnings (Separated by , )' message={formik.errors.learnings} />
<Form.Control asChild>
<Textarea placeholder='Science, Design, Architecture' style={{ backgroundColor: "white" }} onChange={formik.handleChange} value={formik.values.learnings} required />
</Form.Control>
</FormField>
</FormLayout>
</div>
</div>
)
}
export default CourseEdition

View file

@ -1,5 +1,5 @@
'use client';
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse';
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
@ -34,7 +34,7 @@ function CoursesHome(params: CourseProps) {
<BreadCrumbs type='courses' />
<div className='w-100 flex justify-between'>
<div className='flex font-bold text-4xl'>Courses</div>
<div className='pt-3 flex font-bold text-4xl'>Courses</div>
<AuthenticatedClientElement checkMethod='roles'
action='create'
ressourceType='course'

View file

@ -1,19 +1,19 @@
'use client';
import EditCourseStructure from '../../../../../../../../components/DashboardPages/EditCourseStructure/EditCourseStructure'
import BreadCrumbs from '@components/DashboardPages/UI/BreadCrumbs'
import EditCourseStructure from '../../../../../../../../components/Dashboard/EditCourseStructure/EditCourseStructure'
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import PageLoading from '@components/Objects/Loaders/PageLoading';
import ClientComponentSkeleton from '@components/Utils/ClientComp';
import { getAPIUrl, getUriWithOrg } from '@services/config/config';
import { swrFetcher } from '@services/utils/ts/requests';
import React, { createContext, use, useEffect, useState } from 'react'
import useSWR from 'swr';
import { CourseProvider, useCourse } from '../../../../../../../../components/DashboardPages/CourseContext';
import SaveState from '@components/DashboardPages/UI/SaveState';
import { CourseProvider, useCourse } from '../../../../../../../../components/Contexts/CourseContext';
import SaveState from '@components/Dashboard/UI/SaveState';
import Link from 'next/link';
import { CourseOverviewTop } from '@components/DashboardPages/UI/CourseOverviewTop';
import { CourseOverviewTop } from '@components/Dashboard/UI/CourseOverviewTop';
import { CSSTransition } from 'react-transition-group';
import { motion } from 'framer-motion';
import EditCourseGeneral from '@components/DashboardPages/EditCourseGeneral/EditCourseGeneral';
import EditCourseGeneral from '@components/Dashboard/EditCourseGeneral/EditCourseGeneral';
import { GalleryVertical, GalleryVerticalEnd, Info } from 'lucide-react';
export type CourseOverviewParams = {

View file

@ -1,4 +1,4 @@
import LeftMenu from '@components/DashboardPages/UI/LeftMenu'
import LeftMenu from '@components/Dashboard/UI/LeftMenu'
import AuthProvider from '@components/Security/AuthProvider'
import React from 'react'

View file

@ -1,10 +1,17 @@
'use client';
import { OrgProvider } from "@components/Contexts/OrgContext";
import { getAPIUrl } from "@services/config/config";
import { swrFetcher } from "@services/utils/ts/requests";
import "@styles/globals.css";
import useSWR from "swr";
export default function RootLayout({ children, params }: { children: React.ReactNode, params: any }) {
export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) {
return (
<>
<div>
<OrgProvider orgslug={params.orgslug}>
{children}
</>
</OrgProvider>
</div>
);
}