From cb642fded05052f6b508c779e0c116699725f8cf Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 27 May 2023 18:20:18 +0200 Subject: [PATCH] feat: improve collections page --- .../collection/[collectionid]/page.tsx | 72 ++++----- .../(withmenu)/collections/admin.tsx | 73 +++++++++ .../(withmenu)/collections/new/page.tsx | 3 +- .../[orgslug]/(withmenu)/collections/page.tsx | 146 +++++++++--------- .../(withmenu)/course/[courseid]/course.tsx | 3 - .../[orgslug]/(withmenu)/courses/courses.tsx | 1 - front/next.config.js | 1 - front/services/courses/collections.ts | 2 +- 8 files changed, 181 insertions(+), 120 deletions(-) create mode 100644 front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx diff --git a/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx index 0797c455..7a79fa23 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx @@ -6,57 +6,57 @@ import { cookies } from "next/headers"; import Link from "next/link"; type MetadataProps = { - params: { orgslug: string, courseid: string, collectionid: string }; - searchParams: { [key: string]: string | string[] | undefined }; + params: { orgslug: string, courseid: string, collectionid: string }; + searchParams: { [key: string]: string | string[] | undefined }; }; export async function generateMetadata( - { params }: MetadataProps, + { params }: MetadataProps, ): Promise { - 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'] }); - const col = await getCollectionByIdWithAuthHeader(params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); + 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'] }); + const col = await getCollectionByIdWithAuthHeader(params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); + + console.log(col) - console.log(col) - - return { - title: `Collection : ${col.name} — ${org.name}`, - description: `${col.description} `, - }; + return { + title: `Collection : ${col.name} — ${org.name}`, + description: `${col.description} `, + }; } -const CollectionPage = async (params: any) => { - const cookieStore = cookies(); - const access_token_cookie: any = cookieStore.get('access_token_cookie'); - const orgslug = params.params.orgslug; - const col = await getCollectionByIdWithAuthHeader(params.params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); +const CollectionPage = async (params : any) => { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + const orgslug = params.params.orgslug; + const col = await getCollectionByIdWithAuthHeader(params.params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); - const removeCoursePrefix = (courseid: string) => { - return courseid.replace("course_", "") - } + const removeCoursePrefix = (courseid: string) => { + return courseid.replace("course_", "") + } - return
-

Collection

-

{col.name}

-
-
- {col.courses.map((course: any) => ( -
- -
+ return
+

Collection

+

{col.name}

+
+
+ {col.courses.map((course: any) => ( +
+ +
+
+ +

{course.name}

- -

{course.name}

+ ))}
- ))} -
-
; +
; }; export default CollectionPage; \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx new file mode 100644 index 00000000..bb5b9c8d --- /dev/null +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx @@ -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 ( +
+ + +
+ ) + } else { + return ( +
+ ) + } + } + else { + return ( +
+ ) + } +} + +export default CollectionAdminEditsArea; \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx index e1de0cb6..cf94e666 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx @@ -5,7 +5,7 @@ import { Title } from "@components/UI/Elements/Styles/Title"; import { createCollection } from "@services/courses/collections"; import useSWR from "swr"; 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"; function NewCollection(params: any) { @@ -44,6 +44,7 @@ function NewCollection(params: any) { org_id: org.org_id, }; await createCollection(collection); + revalidateTags(["collections"]); router.push(getUriWithOrg(orgslug, "/collections")); }; diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx index a6169647..930b9b1c 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx @@ -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 React from "react"; -import styled from "styled-components"; -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"; +import { Title } from "../courses/courses"; +import CollectionAdminEditsArea from "./admin"; -function Collections(params: any) { - const orgslug = params.params.orgslug; - const { data: collections, error: error } = useSWR(`${getAPIUrl()}collections/page/1/limit/10`, swrFetcher); +type MetadataProps = { + params: { orgslug: string, courseid: string }; + searchParams: { [key: string]: string | string[] | undefined }; +}; - async function deleteCollectionAndFetch(collectionId: number) { - await deleteCollection(collectionId); - mutate(`${getAPIUrl()}collections/page/1/limit/10`); - } +export async function generateMetadata( + { params }: MetadataProps, +): Promise { + 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 ( - <> - - {orgslug} Collections :{" "} - <Link href={getUriWithOrg(orgslug, "/collections/new")}> - <button>+</button> - </Link>{" "} - - {error &&

Failed to load

} - {!collections ? ( -
Loading...
- ) : ( -
- {collections.map((collection: any) => ( - - {collection.name} - - {collection.courses.map((course: any) => ( - - {course.name} - - ))} - - - - ))} -
- )} - - ); + return { + title: `Collections — ${org.name}`, + description: `Collections of courses from ${org.name}`, + }; } -const CollectionItem = styled.div` - 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 removeCollectionPrefix = (collectionid: string) => { + return collectionid.replace("collection_", "") +} -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; +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 ( +
+
+ + <Link className="flex justify-center" href={getUriWithOrg(orgslug, "/collections/new")}> + <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> + </div> + <div className="home_collections flex flex-wrap"> + {collections.map((collection: any) => ( + <div className="pr-8 flex flex-col" key={collection.collection_id}> + <CollectionAdminEditsArea collection_id={collection.collection_id} collection={collection} /> + <Link href={getUriWithOrg(orgslug, "/collection/" + removeCollectionPrefix(collection.collection_id))}> + <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" > + <h1 className="font-bold text-lg py-2 justify-center mb-2">{collection.name}</h1> + <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> + ))} + </div> + </div> + </Link> + </div> + ))} + </div> + </div> + ); +} + +export default CollectionsPage \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx index aa1678d4..d88b6ce8 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx @@ -18,7 +18,6 @@ const CourseClient = (props: any) => { async function startCourseUI() { // Create activity await startCourse("course_" + courseid, orgslug); - revalidateTags(['courses']); } @@ -26,8 +25,6 @@ const CourseClient = (props: any) => { // Close activity let activity = await removeCourse("course_" + courseid, orgslug); - console.log(activity); - // Mutate course revalidateTags(['courses']); } diff --git a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx index 079cc54a..018f731a 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx @@ -63,7 +63,6 @@ function Courses(props: CourseProps) { <div className="flex space-x-5"> - {courses.map((course: any) => ( <div key={course.course_id}> <AdminEditsArea course={course} orgslug={orgslug} course_id={course.course_id} deleteCourses={deleteCourses} /> diff --git a/front/next.config.js b/front/next.config.js index b8c0a4e7..e1035183 100644 --- a/front/next.config.js +++ b/front/next.config.js @@ -7,7 +7,6 @@ const { withSentryConfig } = require('@sentry/nextjs'); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: false, - swcMinify: false, compiler: { styledComponents: true, }, diff --git a/front/services/courses/collections.ts b/front/services/courses/collections.ts index 4d99f19b..ad0bf20c 100644 --- a/front/services/courses/collections.ts +++ b/front/services/courses/collections.ts @@ -42,7 +42,7 @@ export async function getOrgCollections() { } 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); return res; }