mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: improve collections page
This commit is contained in:
parent
27cd84127f
commit
cb642fded0
8 changed files with 181 additions and 120 deletions
73
front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx
Normal file
73
front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { AuthContext } from '@components/Security/AuthProvider';
|
||||||
|
import { deleteCollection } from '@services/courses/collections';
|
||||||
|
import { revalidateTags } from '@services/utils/ts/requests';
|
||||||
|
import { Link, Trash } from 'lucide-react';
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const CollectionAdminEditsArea = (props: any) => {
|
||||||
|
const org_roles_values = ["admin", "owner"];
|
||||||
|
const user_roles_values = ["role_admin"];
|
||||||
|
console.log("props: ", props);
|
||||||
|
|
||||||
|
const auth: any = React.useContext(AuthContext);
|
||||||
|
console.log("auth: ", auth);
|
||||||
|
|
||||||
|
|
||||||
|
// this is amazingly terrible code, but gotta release that MVP
|
||||||
|
// TODO: fix this
|
||||||
|
|
||||||
|
function isAuthorized() {
|
||||||
|
const org_id = props.collection.org_id;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionUI = async (collectionId: number) => {
|
||||||
|
await deleteCollection(collectionId);
|
||||||
|
revalidateTags(["collections"]);
|
||||||
|
// reload the page
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is amazingly terrible code, but gotta release that MVP
|
||||||
|
// TODO: fix this
|
||||||
|
|
||||||
|
if (auth.isAuthenticated) {
|
||||||
|
if (isAuthorized()) {
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-2 py-2">
|
||||||
|
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" onClick={() => deleteCollectionUI(props.collection_id)}>
|
||||||
|
Delete <Trash size={10}></Trash>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<div></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CollectionAdminEditsArea;
|
||||||
|
|
@ -5,7 +5,7 @@ import { Title } from "@components/UI/Elements/Styles/Title";
|
||||||
import { createCollection } from "@services/courses/collections";
|
import { createCollection } from "@services/courses/collections";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { getAPIUrl, getUriWithOrg } from "@services/config/config";
|
import { getAPIUrl, getUriWithOrg } from "@services/config/config";
|
||||||
import { swrFetcher } from "@services/utils/ts/requests";
|
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
|
||||||
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
||||||
|
|
||||||
function NewCollection(params: any) {
|
function NewCollection(params: any) {
|
||||||
|
|
@ -44,6 +44,7 @@ function NewCollection(params: any) {
|
||||||
org_id: org.org_id,
|
org_id: org.org_id,
|
||||||
};
|
};
|
||||||
await createCollection(collection);
|
await createCollection(collection);
|
||||||
|
revalidateTags(["collections"]);
|
||||||
router.push(getUriWithOrg(orgslug, "/collections"));
|
router.push(getUriWithOrg(orgslug, "/collections"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,85 +1,77 @@
|
||||||
"use client";
|
import { getBackendUrl, getUriWithOrg } from "@services/config/config";
|
||||||
|
import { deleteCollection, getOrgCollectionsWithAuthHeader } from "@services/courses/collections";
|
||||||
|
import { getCourseMetadataWithAuthHeader } from "@services/courses/courses";
|
||||||
|
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
||||||
|
import { revalidateTags } from "@services/utils/ts/requests";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { revalidateTag } from "next/cache";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React from "react";
|
import { Title } from "../courses/courses";
|
||||||
import styled from "styled-components";
|
import CollectionAdminEditsArea from "./admin";
|
||||||
import { Title } from "@components/UI/Elements/Styles/Title";
|
|
||||||
import { deleteCollection } from "@services/courses/collections";
|
|
||||||
import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config";
|
|
||||||
import { swrFetcher } from "@services/utils/ts/requests";
|
|
||||||
import useSWR, { mutate } from "swr";
|
|
||||||
|
|
||||||
function Collections(params: any) {
|
type MetadataProps = {
|
||||||
const orgslug = params.params.orgslug;
|
params: { orgslug: string, courseid: string };
|
||||||
const { data: collections, error: error } = useSWR(`${getAPIUrl()}collections/page/1/limit/10`, swrFetcher);
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
};
|
||||||
|
|
||||||
async function deleteCollectionAndFetch(collectionId: number) {
|
export async function generateMetadata(
|
||||||
await deleteCollection(collectionId);
|
{ params }: MetadataProps,
|
||||||
mutate(`${getAPIUrl()}collections/page/1/limit/10`);
|
): Promise<Metadata> {
|
||||||
|
const cookieStore = cookies();
|
||||||
|
const access_token_cookie: any = cookieStore.get('access_token_cookie');
|
||||||
|
// Get Org context information
|
||||||
|
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `Collections — ${org.name}`,
|
||||||
|
description: `Collections of courses from ${org.name}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeCollectionPrefix = (collectionid: string) => {
|
||||||
|
return collectionid.replace("collection_", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CollectionsPage = async (params: any) => {
|
||||||
|
const cookieStore = cookies();
|
||||||
|
const access_token_cookie: any = cookieStore.get('access_token_cookie');
|
||||||
|
const orgslug = params.params.orgslug;
|
||||||
|
const collections = await getOrgCollectionsWithAuthHeader(access_token_cookie ? access_token_cookie.value : null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="max-w-7xl mx-auto px-4 py-10" >
|
||||||
<Title>
|
<div className="flex justify-between" >
|
||||||
{orgslug} Collections :{" "}
|
<Title title="Collections" type="col" />
|
||||||
<Link href={getUriWithOrg(orgslug, "/collections/new")}>
|
<Link className="flex justify-center" href={getUriWithOrg(orgslug, "/collections/new")}>
|
||||||
<button>+</button>
|
<button className="rounded-md bg-black antialiased ring-offset-purple-800 p-2 px-5 my-auto font text-sm font-bold text-white drop-shadow-lg">Add Collection + </button>
|
||||||
</Link>{" "}
|
</Link>
|
||||||
</Title>
|
</div>
|
||||||
{error && <p>Failed to load</p>}
|
<div className="home_collections flex flex-wrap">
|
||||||
{!collections ? (
|
|
||||||
<div>Loading...</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{collections.map((collection: any) => (
|
{collections.map((collection: any) => (
|
||||||
<CollectionItem key={collection.collection_id}>
|
<div className="pr-8 flex flex-col" key={collection.collection_id}>
|
||||||
<Link href={"/org/" + orgslug + "/collections/" + collection.collection_id}>{collection.name}</Link>
|
<CollectionAdminEditsArea collection_id={collection.collection_id} collection={collection} />
|
||||||
<CourseMiniThumbnail>
|
<Link href={getUriWithOrg(orgslug, "/collection/" + removeCollectionPrefix(collection.collection_id))}>
|
||||||
{collection.courses.map((course: any) => (
|
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-[249px] h-[180px] bg-cover flex flex-col items-center justify-center bg-indigo-600 font-bold text-zinc-50" >
|
||||||
<Link key={course.course_id} href={"/org/" + orgslug + "/course/" + course.course_id.substring(7)}>
|
<h1 className="font-bold text-lg py-2 justify-center mb-2">{collection.name}</h1>
|
||||||
<img key={course.course_id} src={`${getBackendUrl()}content/uploads/img/${course.thumbnail}`} alt={course.name} />
|
<div className="flex -space-x-4">
|
||||||
|
{collection.courses.slice(0, 3).map((course: any) => (
|
||||||
|
<Link key={course.course_id} href={getUriWithOrg(orgslug, "/course/" + course.course_id.substring(7))}>
|
||||||
|
<img className="w-12 h-12 rounded-full flex items-center justify-center shadow-lg ring-2 ring-white z-50" key={course.course_id} src={`${getBackendUrl()}content/uploads/img/${course.thumbnail}`} alt={course.name} />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</CourseMiniThumbnail>
|
</div>
|
||||||
<button onClick={() => deleteCollectionAndFetch(collection.collection_id)}>Delete</button>
|
</div>
|
||||||
</CollectionItem>
|
</Link>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionItem = styled.div`
|
export default CollectionsPage
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CourseMiniThumbnail = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
img {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 5px;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export default Collections;
|
|
||||||
|
|
@ -18,7 +18,6 @@ const CourseClient = (props: any) => {
|
||||||
async function startCourseUI() {
|
async function startCourseUI() {
|
||||||
// Create activity
|
// Create activity
|
||||||
await startCourse("course_" + courseid, orgslug);
|
await startCourse("course_" + courseid, orgslug);
|
||||||
|
|
||||||
revalidateTags(['courses']);
|
revalidateTags(['courses']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,8 +25,6 @@ const CourseClient = (props: any) => {
|
||||||
|
|
||||||
// Close activity
|
// Close activity
|
||||||
let activity = await removeCourse("course_" + courseid, orgslug);
|
let activity = await removeCourse("course_" + courseid, orgslug);
|
||||||
console.log(activity);
|
|
||||||
|
|
||||||
// Mutate course
|
// Mutate course
|
||||||
revalidateTags(['courses']);
|
revalidateTags(['courses']);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ function Courses(props: CourseProps) {
|
||||||
|
|
||||||
|
|
||||||
<div className="flex space-x-5">
|
<div className="flex space-x-5">
|
||||||
|
|
||||||
{courses.map((course: any) => (
|
{courses.map((course: any) => (
|
||||||
<div key={course.course_id}>
|
<div key={course.course_id}>
|
||||||
<AdminEditsArea course={course} orgslug={orgslug} course_id={course.course_id} deleteCourses={deleteCourses} />
|
<AdminEditsArea course={course} orgslug={orgslug} course_id={course.course_id} deleteCourses={deleteCourses} />
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ const { withSentryConfig } = require('@sentry/nextjs');
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
swcMinify: false,
|
|
||||||
compiler: {
|
compiler: {
|
||||||
styledComponents: true,
|
styledComponents: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export async function getOrgCollections() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrgCollectionsWithAuthHeader(access_token: string) {
|
export async function getOrgCollectionsWithAuthHeader(access_token: string) {
|
||||||
const result: any = await fetch(`${getAPIUrl()}collections/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, { revalidate: 10 }, access_token));
|
const result: any = await fetch(`${getAPIUrl()}collections/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, { revalidate: 3 }, access_token));
|
||||||
const res = await errorHandling(result);
|
const res = await errorHandling(result);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue