mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: frontend create & delete collections
This commit is contained in:
parent
bca6c3000a
commit
ce785fd078
6 changed files with 255 additions and 9 deletions
|
|
@ -17,9 +17,7 @@ export const Menu = () => {
|
||||||
<Logo>
|
<Logo>
|
||||||
<Image width={25} height={25} src={learnhouseIcon} alt="" />
|
<Image width={25} height={25} src={learnhouseIcon} alt="" />
|
||||||
<Link href={"/"}>
|
<Link href={"/"}>
|
||||||
|
|
||||||
<Image width={108} height={28} src={learnhouseLogo} alt="" />
|
<Image width={108} height={28} src={learnhouseLogo} alt="" />
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
</Logo>
|
</Logo>
|
||||||
<div id="accounts"></div>
|
<div id="accounts"></div>
|
||||||
|
|
@ -32,11 +30,11 @@ export const Menu = () => {
|
||||||
<MenuArea>
|
<MenuArea>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<Link href={"/org/" + orgslug + "/courses"}>
|
<Link href={"/org/" + orgslug + "/courses"}>Courses</Link>
|
||||||
Courses
|
</li>
|
||||||
</Link>
|
<li>
|
||||||
|
<Link href={"/org/" + orgslug + "/collections"}>Collections</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>Collections</li>
|
|
||||||
<li>Activity</li>
|
<li>Activity</li>
|
||||||
<li>More</li>
|
<li>More</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
78
front/pages/org/[orgslug]/collections/index.tsx
Normal file
78
front/pages/org/[orgslug]/collections/index.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<Layout>
|
||||||
|
<Title>
|
||||||
|
{orgslug} Collections :{" "}
|
||||||
|
<Link href={"/org/" + orgslug + "/collections/new"}>
|
||||||
|
<button>+</button>
|
||||||
|
</Link>{" "}
|
||||||
|
</Title>
|
||||||
|
{isLoading ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{collections.map((collection: any) => (
|
||||||
|
<CollectionItem key={collection.collection_id}>
|
||||||
|
<Link href={"/org/" + orgslug + "/collections/" + collection.collection_id}>{collection.name}</Link>
|
||||||
|
<button onClick={() => deleteCollectionAndFetch(collection.collection_id)}>Delete</button>
|
||||||
|
</CollectionItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
95
front/pages/org/[orgslug]/collections/new/index.tsx
Normal file
95
front/pages/org/[orgslug]/collections/new/index.tsx
Normal file
|
|
@ -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<HTMLInputElement>) => {
|
||||||
|
setName(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDescriptionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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 (
|
||||||
|
<Layout>
|
||||||
|
<Title>Add new</Title>
|
||||||
|
<br />
|
||||||
|
<input type="text" placeholder="Name" value={name} onChange={handleNameChange} />
|
||||||
|
{isLoading ? (
|
||||||
|
<p>Loading...</p>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{courses.map((course: any) => (
|
||||||
|
<div key={course.course_id}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={course.course_id}
|
||||||
|
name={course.course_id}
|
||||||
|
value={course.course_id}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedCourses([...selectedCourses, e.target.value]);
|
||||||
|
} else {
|
||||||
|
setSelectedCourses(selectedCourses.filter((item: any) => item !== e.target.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={course.course_id}>{course.name}</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<input type="text" placeholder="Description" value={description} onChange={handleDescriptionChange} />
|
||||||
|
<br />
|
||||||
|
<button onClick={handleSubmit}>Submit</button>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewCollection;
|
||||||
73
front/services/collections.ts
Normal file
73
front/services/collections.ts
Normal file
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -238,7 +238,7 @@ async def verify_rights(course_id: str, current_user: PublicUser, action: str):
|
||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
raise HTTPException(
|
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)
|
hasRoleRights = await verify_user_rights_with_roles(action, current_user.user_id, course_id)
|
||||||
isAuthor = current_user.user_id in course["authors"]
|
isAuthor = current_user.user_id in course["authors"]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class Collection(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
courses: List[str] # course_id
|
courses: List[str] # course_id
|
||||||
|
org_id: str # org_id
|
||||||
|
|
||||||
|
|
||||||
class CollectionInDB(Collection):
|
class CollectionInDB(Collection):
|
||||||
|
|
@ -51,7 +52,8 @@ async def create_collection(collection_object: Collection, current_user: PublicU
|
||||||
# find if collection already exists using name
|
# find if collection already exists using name
|
||||||
isCollectionNameAvailable = collections.find_one({"name": collection_object.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:
|
if isCollectionNameAvailable:
|
||||||
raise HTTPException(
|
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})
|
collection = collections.find_one({"collection_id": collection_id})
|
||||||
|
|
||||||
if not collection:
|
if not collection and action != "create":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue