Merge pull request #112 from learnhouse/swve/eng-102-edit-course-delete-activity-obfuscate-editing-non-dynamic

Add delete activity option + redesign edit
This commit is contained in:
Badr B 2023-08-25 21:20:36 +02:00 committed by GitHub
commit cd1152022d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 54 deletions

View file

@ -68,13 +68,20 @@ def get_learnhouse_config() -> LearnHouseConfig:
yaml_config = yaml.safe_load(f)
# General Config
env_development_mode = os.environ.get("LEARNHOUSE_DEVELOPMENT_MODE")
development_mode = env_development_mode or yaml_config.get("general", {}).get(
"development_mode"
# Development Mode & Install Mode
env_development_mode = eval(os.environ.get("LEARNHOUSE_DEVELOPMENT_MODE", "None"))
development_mode = (
env_development_mode
if env_development_mode is not None
else yaml_config.get("general", {}).get("development_mode")
)
env_install_mode = os.environ.get("LEARNHOUSE_INSTALL_MODE")
install_mode = env_install_mode or yaml_config.get("general", {}).get(
"install_mode"
env_install_mode = os.environ.get("LEARNHOUSE_INSTALL_MODE", "None")
install_mode = (
env_install_mode
if env_install_mode is not None
else yaml_config.get("general", {}).get("install_mode")
)
# Security Config

View file

@ -6,7 +6,7 @@ import ConfirmationModal from '@components/StyledElements/ConfirmationModal/Conf
import { getUriWithOrg } from '@services/config/config';
import { deleteCollection } from '@services/courses/collections';
import { revalidateTags } from '@services/utils/ts/requests';
import { Link, Trash } from 'lucide-react';
import { Link, Trash, X } from 'lucide-react';
import { useRouter } from 'next/navigation';
import React from 'react'
@ -26,15 +26,17 @@ const CollectionAdminEditsArea = (props: any) => {
return (
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
<div className="flex space-x-2 py-2">
<div className="flex space-x-2 relative top-8 z-20 left-2">
<ConfirmationModal
confirmationMessage="Are you sure you want to delete this collection?"
confirmationButtonText="Delete Collection"
dialogTitle={"Delete " + props.collection.name + " ?"}
dialogTrigger={
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
Delete <Trash size={10}></Trash>
</button>}
<div
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
</div>}
functionToExecute={() => deleteCollectionUI(props.collection_id)}
status='warning'
></ConfirmationModal>

View file

@ -11,7 +11,7 @@ import { createActivity, createFileActivity, createExternalVideoActivity } from
import { getOrganizationContextInfo } from "@services/organizations/orgs";
import Modal from "@components/StyledElements/Modal/Modal";
import { denyAccessToUser } from "@services/utils/react/middlewares/views";
import { Folders, SaveIcon } from "lucide-react";
import { Folders, Hexagon, SaveIcon } from "lucide-react";
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
import { mutate } from "swr";
@ -81,6 +81,8 @@ function CourseContentEdition(props: any) {
router.refresh();
};
// Submit File Upload
const submitFileActivity = async (file: any, type: any, activity: any, chapterId: string) => {
await updateChaptersMetadata(courseid, data);
@ -301,7 +303,7 @@ function CourseContentEdition(props: any) {
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">
<Folders size={16} />
<Hexagon size={16} />
<div>Add chapter +</div>
</div>
}

View file

@ -16,6 +16,7 @@ import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentT
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
import { Pencil, X } from 'lucide-react';
interface CourseProps {
orgslug: string;
@ -91,22 +92,26 @@ function Courses(props: CourseProps) {
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
return (
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-2 py-2">
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-1 relative top-8 z-20 left-2">
<ConfirmationModal
confirmationButtonText='Delete Course'
confirmationMessage='Are you sure you want to delete this course?'
dialogTitle={'Delete ' + props.course.name + ' ?'}
dialogTrigger={
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
Delete
</button>}
<div
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
</div>}
functionToExecute={() => props.deleteCourses(props.courseId)}
status='warning'
></ConfirmationModal>
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
<button className="rounded-md text-sm px-3 font-bold text-orange-800 bg-orange-200 w-16 flex justify-center items-center">
Edit
</button>
<div
className=" hover:cursor-pointer p-1 px-4 bg-orange-600 rounded-md"
rel="noopener noreferrer">
<Pencil size={15} className="text-orange-200 font-bold" />
</div>
</Link>
</div>
</AuthenticatedClientElement>

View file

@ -1,36 +1,74 @@
import Link from "next/link";
import { Draggable } from "react-beautiful-dnd";
import { EyeOpenIcon, Pencil2Icon } from '@radix-ui/react-icons'
import { getUriWithOrg } from "@services/config/config";
import { FileText, Video, Sparkles, File } from "lucide-react";
import { EyeOpenIcon, Pencil2Icon, TrashIcon } from '@radix-ui/react-icons'
import { getAPIUrl, getUriWithOrg } from "@services/config/config";
import { FileText, Video, Sparkles, XSquare, X, Pencil, MoreVertical, Eye } from "lucide-react";
import { mutate } from "swr";
import { revalidateTags } from "@services/utils/ts/requests";
import { useRouter } from "next/navigation";
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
import { deleteActivity } from "@services/courses/activities";
function Activity(props: any) {
const router = useRouter();
async function removeActivity() {
await deleteActivity(props.activity.id);
mutate(`${getAPIUrl()}chapters/meta/course_${props.courseid}`);
await revalidateTags(['courses'], props.orgslug);
router.refresh();
}
return (
<Draggable key={props.activity.id} draggableId={props.activity.id} 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-2 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}>
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}>
<div className="px-3 text-gray-300 space-x-1 w-28" >
{props.activity.type === "video" && <><div className="flex space-x-2 items-center"><Video size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Video</div> </div></>}
{props.activity.type === "documentpdf" && <><div className="flex space-x-2 items-center"><FileText size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Document</div> </div></>}
{props.activity.type === "dynamic" && <><div className="flex space-x-2 items-center"><Sparkles size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Dynamic</div> </div></>}
</div>
<div className="grow items-center space-x-1 flex">
<p className="first-letter:uppercase"> {props.activity.name} </p>
</div>
<div className="flex flex-row space-x-1 px-3"><Link
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`}
className=" hover:cursor-pointer p-1 rounded-md "
rel="noopener noreferrer">
<EyeOpenIcon className="text-gray-500" />
</Link>
<div className="flex flex-row space-x-2">
{props.activity.type === "dynamic" && <>
<Link
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}/edit`}
className=" hover:cursor-pointer p-1 rounded-md "
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
rel="noopener noreferrer">
<Pencil2Icon className="text-gray-500" />
</Link></div>
<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_", "")}`}
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" />
</Link>
</div>
<div className="flex flex-row pr-3 space-x-1 items-center">
<MoreVertical size={15} className="text-gray-300" />
<ConfirmationModal
confirmationMessage="Are you sure you want to delete this activity ?"
confirmationButtonText="Delete Activity"
dialogTitle={"Delete " + props.activity.name + " ?"}
dialogTrigger={
<div
className=" hover:cursor-pointer p-1 px-5 bg-red-600 rounded-md"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
</div>}
functionToExecute={() => removeActivity()}
status='warning'
></ConfirmationModal></div>
</div>
)}
</Draggable>

View file

@ -2,7 +2,7 @@ import React from "react";
import styled from "styled-components";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Activity from "./Activity";
import { PlusSquare, Sparkle, Sparkles, Trash, Trash2 } from "lucide-react";
import { Folders, Hexagon, MoreVertical, PlusSquare, Sparkle, Sparkles, Trash, Trash2, X } from "lucide-react";
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
function Chapter(props: any) {
@ -14,25 +14,34 @@ function Chapter(props: any) {
{...provided.draggableProps}
ref={provided.innerRef}
// isDragging={snapshot.isDragging}
className="max-w-screen-2xl mx-auto"
className="max-w-screen-2xl mx-auto bg-white px-5"
key={props.info.list.chapter.id}
>
<h3 className="flex space-x-2 pt-3 font-bold text-md items-center">
<p>{props.info.list.chapter.name}
</p>
<div className="flex pt-3 pr-3 font-bold text-md items-center space-x-2">
<div className="flex grow text-lg space-x-3 items-center rounded-md px-3 py-1">
<div className="bg-neutral-100 rounded-md p-2">
<Hexagon strokeWidth={3} size={16} className="text-neutral-600 " />
</div>
<p className="text-neutral-700 first-letter:uppercase">{props.info.list.chapter.name}</p>
</div>
<MoreVertical size={15} className="text-gray-300" />
<ConfirmationModal
confirmationButtonText="Delete Chapter"
confirmationMessage="Are you sure you want to delete this chapter?"
dialogTitle={"Delete " + props.info.list.chapter.name + " ?"}
dialogTrigger={
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
Delete <Trash size={10}></Trash>
</button>}
<div
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md shadow flex space-x-1 items-center text-rose-100 text-sm"
rel="noopener noreferrer">
<X size={15} className="text-rose-200 font-bold" />
<p>Delete Chapter</p>
</div>}
functionToExecute={() => props.deleteChapter(props.info.list.chapter.id)}
status='warning'
></ConfirmationModal>
</h3>
</div>
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity">
{(provided) => (
<ActivitiesList {...provided.droppableProps} ref={provided.innerRef}>
@ -62,8 +71,7 @@ function Chapter(props: any) {
const ChapterWrapper = styled.div`
margin-bottom: 20px;
padding: 4px;
background-color: #ffffff9d;
padding: 12px;
font-size: 15px;
display: block;
border-radius: 9px;

View file

@ -52,6 +52,12 @@ export async function getActivity(activity_id: any, next: any) {
return res;
}
export async function deleteActivity(activity_id: any) {
const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("DELETE", null, null));
const res = await result.json();
return res;
}
export async function getActivityWithAuthHeader(activity_id: any, next: any, access_token: string) {
const result = await fetch(`${getAPIUrl()}activities/activity_${activity_id}`, RequestBodyWithAuthHeader("GET", null, next, access_token));
const res = await result.json();

View file

@ -162,10 +162,18 @@ async def delete_activity(request: Request, activity_id: str, current_user: Publ
status_code=status.HTTP_409_CONFLICT, detail="activity does not exist"
)
# Remove Activity
isDeleted = await activities.delete_one({"activity_id": activity_id})
if isDeleted:
return {"detail": "activity deleted"}
# Remove Activity from chapter
courses = request.app.db["courses"]
isDeletedFromChapter = await courses.update_one(
{"chapters_content.activities": activity_id},
{"$pull": {"chapters_content.$.activities": activity_id}},
)
if isDeleted and isDeletedFromChapter:
return {"detail": "Activity deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,

View file

@ -9,10 +9,10 @@ def isDevModeEnabled():
else:
return False
def isDevModeEnabledOrRaise():
config = get_learnhouse_config()
if config.general_config.development_mode:
return True
else:
raise HTTPException(status_code=403, detail="Development mode is not enabled")
raise HTTPException(status_code=403, detail="Development mode is disabled")