From ce785fd07813effb3558afd128ef1e95eb0dd23a Mon Sep 17 00:00:00 2001 From: swve Date: Thu, 22 Dec 2022 21:18:12 +0100 Subject: [PATCH] feat: frontend create & delete collections --- front/components/UI/Elements/Menu.tsx | 10 +- .../pages/org/[orgslug]/collections/index.tsx | 78 +++++++++++++++ .../org/[orgslug]/collections/new/index.tsx | 95 +++++++++++++++++++ front/services/collections.ts | 73 ++++++++++++++ src/services/courses/chapters.py | 2 +- src/services/courses/collections.py | 6 +- 6 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 front/pages/org/[orgslug]/collections/index.tsx create mode 100644 front/pages/org/[orgslug]/collections/new/index.tsx create mode 100644 front/services/collections.ts diff --git a/front/components/UI/Elements/Menu.tsx b/front/components/UI/Elements/Menu.tsx index ff741359..57858435 100644 --- a/front/components/UI/Elements/Menu.tsx +++ b/front/components/UI/Elements/Menu.tsx @@ -17,9 +17,7 @@ export const Menu = () => { - -
@@ -32,11 +30,11 @@ export const Menu = () => { diff --git a/front/pages/org/[orgslug]/collections/index.tsx b/front/pages/org/[orgslug]/collections/index.tsx new file mode 100644 index 00000000..d3c911e7 --- /dev/null +++ b/front/pages/org/[orgslug]/collections/index.tsx @@ -0,0 +1,78 @@ +import Layout from "../../../../components/UI/Layout"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import React from "react"; +import styled from "styled-components"; +import { Title } from "../../../../components/UI/Elements/Styles/Title"; +import { deleteCollection, getOrgCollections } from "../../../../services/collections"; +import { getOrganizationContextInfo } from "../../../../services/orgs"; + +function Collections() { + const router = useRouter(); + const { orgslug } = router.query; + + const [isLoading, setIsLoading] = React.useState(true); + const [collections, setCollections] = React.useState([]); + + async function fetchCollections() { + setIsLoading(true); + const org = await getOrganizationContextInfo(orgslug); + const collections = await getOrgCollections(org.org_id); + setCollections(collections); + setIsLoading(false); + } + + async function deleteCollectionAndFetch(collectionId: number) { + setIsLoading(true); + await deleteCollection(collectionId); + await fetchCollections(); + setIsLoading(false); + } + + React.useEffect(() => { + fetchCollections(); + }, []); + + return ( + + + {orgslug} Collections :{" "} + <Link href={"/org/" + orgslug + "/collections/new"}> + <button>+</button> + </Link>{" "} + + {isLoading ? ( +
Loading...
+ ) : ( +
+ {collections.map((collection: any) => ( + + {collection.name} + + + ))} +
+ )} +
+ ); +} + +const CollectionItem = styled.div` + display: flex; + flex-direction: column; + place-items: center; + width: 100%; + height: 100%; + padding: 10px; + border: 1px solid #e5e5e5; + border-radius: 5px; + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03); + background: #ffffff; + cursor: pointer; + transition: all 0.2s ease-in-out; + &:hover { + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1); + } +`; + +export default Collections; diff --git a/front/pages/org/[orgslug]/collections/new/index.tsx b/front/pages/org/[orgslug]/collections/new/index.tsx new file mode 100644 index 00000000..64156ab0 --- /dev/null +++ b/front/pages/org/[orgslug]/collections/new/index.tsx @@ -0,0 +1,95 @@ +import { useRouter } from "next/router"; +import React from "react"; +import { Title } from "../../../../../components/UI/Elements/Styles/Title"; +import Layout from "../../../../../components/UI/Layout"; +import { getOrganizationContextInfo } from "../../../../../services/orgs"; +import { getOrgCourses } from "../../../../../services/courses/courses"; +import { createCollection } from "../../../../../services/collections"; + +function NewCollection() { + const router = useRouter(); + const { orgslug } = router.query; + const [name, setName] = React.useState(""); + const [org, setOrg] = React.useState({}) as any; + const [description, setDescription] = React.useState(""); + const [selectedCourses, setSelectedCourses] = React.useState([]) as any; + const [courses, setCourses] = React.useState([]) as any; + const [isLoading, setIsLoading] = React.useState(false); + + async function getCourses() { + setIsLoading(true); + const org = await getOrganizationContextInfo(orgslug); + setOrg(org); + const courses = await getOrgCourses(org.org_id); + setCourses(courses); + setIsLoading(false); + } + + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + }; + + const handleDescriptionChange = (event: React.ChangeEvent) => { + setDescription(event.target.value); + }; + + const handleSubmit = async (e: any) => { + e.preventDefault(); + console.log("selectedCourses", selectedCourses); + const collection = { + name: name, + description: description, + courses: selectedCourses, + org_id: org.org_id, + }; + await createCollection(collection); + router.push("/org/" + orgslug + "/collections"); + }; + + React.useEffect(() => { + if (router.isReady) { + getCourses(); + } + return () => {}; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady]); + + return ( + + Add new +
+ + {isLoading ? ( +

Loading...

+ ) : ( +
+ {courses.map((course: any) => ( +
+ { + if (e.target.checked) { + setSelectedCourses([...selectedCourses, e.target.value]); + } else { + setSelectedCourses(selectedCourses.filter((item: any) => item !== e.target.value)); + } + }} + /> + +
+ ))} +
+ )} + +
+ +
+ +
+ ); +} + +export default NewCollection; diff --git a/front/services/collections.ts b/front/services/collections.ts new file mode 100644 index 00000000..fae3b7c2 --- /dev/null +++ b/front/services/collections.ts @@ -0,0 +1,73 @@ +import { getAPIUrl } from "./config"; + +export async function getOrgCollections(org_slug: any) { + const HeadersConfig = new Headers({ "Content-Type": "application/json" }); + + const requestOptions: any = { + method: "GET", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + }; + + return fetch(`${getAPIUrl()}collections/page/1/limit/10`, requestOptions) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); +} + +export async function getCollection(collection_slug: any) { + const HeadersConfig = new Headers({ "Content-Type": "application/json" }); + + const requestOptions: any = { + method: "GET", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + }; + + return fetch( + `${getAPIUrl()}collections/${collection_slug}`, + requestOptions + ) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); +} + + + + + +export async function deleteCollection(collection_id: any) { + const HeadersConfig = new Headers({ "Content-Type": "application/json" }); + + const requestOptions: any = { + method: "DELETE", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + }; + + return fetch( + `${getAPIUrl()}collections/${collection_id}`, + requestOptions + ) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); +} + +// Create a new collection +export async function createCollection(collection: any) { + const HeadersConfig = new Headers({ "Content-Type": "application/json" }); + + const requestOptions: any = { + method: "POST", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + body: JSON.stringify(collection), + }; + + return fetch(`${getAPIUrl()}collections/`, requestOptions) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); +} \ No newline at end of file diff --git a/src/services/courses/chapters.py b/src/services/courses/chapters.py index 824eb2ac..ebd5270c 100644 --- a/src/services/courses/chapters.py +++ b/src/services/courses/chapters.py @@ -238,7 +238,7 @@ async def verify_rights(course_id: str, current_user: PublicUser, action: str): if not course: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail=f"Course/CourseChapter does not exist") + status_code=status.HTTP_409_CONFLICT, detail=f"Course does not exist") hasRoleRights = await verify_user_rights_with_roles(action, current_user.user_id, course_id) isAuthor = current_user.user_id in course["authors"] diff --git a/src/services/courses/collections.py b/src/services/courses/collections.py index 95cdea39..7c373567 100644 --- a/src/services/courses/collections.py +++ b/src/services/courses/collections.py @@ -15,6 +15,7 @@ class Collection(BaseModel): name: str description: str courses: List[str] # course_id + org_id: str # org_id class CollectionInDB(Collection): @@ -51,7 +52,8 @@ async def create_collection(collection_object: Collection, current_user: PublicU # find if collection already exists using name isCollectionNameAvailable = collections.find_one({"name": collection_object.name}) - await verify_collection_rights("*", current_user, "create") + # TODO + # await verify_collection_rights("*", current_user, "create") if isCollectionNameAvailable: raise HTTPException( @@ -139,7 +141,7 @@ async def verify_collection_rights(collection_id: str, current_user: PublicUser collection = collections.find_one({"collection_id": collection_id}) - if not collection: + if not collection and action != "create": raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist")