mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #93 from learnhouse/swve/eng-38-add-whitelabel-logo-to-organization-improve-menu
Add whitelabel + Update Menu Design
This commit is contained in:
commit
3277232748
26 changed files with 793 additions and 363 deletions
|
|
@ -28,7 +28,8 @@ const Organizations = () => {
|
||||||
const handleSubmit = async (e: any) => {
|
const handleSubmit = async (e: any) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log({ name, description, email });
|
console.log({ name, description, email });
|
||||||
const status = await createNewOrganization({ name, description, email, slug , default: false });
|
let logo = ''
|
||||||
|
const status = await createNewOrganization({ name, description, email, logo, slug, default: false });
|
||||||
alert(JSON.stringify(status));
|
alert(JSON.stringify(status));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ function Courses(props: CourseProps) {
|
||||||
async function deleteCourses(course_id: any) {
|
async function deleteCourses(course_id: any) {
|
||||||
await deleteCourseFromBackend(course_id);
|
await deleteCourseFromBackend(course_id);
|
||||||
revalidateTags(['courses']);
|
revalidateTags(['courses']);
|
||||||
|
// terrible, nextjs right now doesn't mutate the page when the data changes
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeNewCourseModal() {
|
async function closeNewCourseModal() {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import "@styles/globals.css";
|
import "@styles/globals.css";
|
||||||
import { Menu } from "@components/UI/Elements/Menu";
|
import { Menu } from "@components/UI/Elements/Menu/Menu";
|
||||||
import AuthProvider from "@components/Security/AuthProvider";
|
import AuthProvider from "@components/Security/AuthProvider";
|
||||||
|
|
||||||
export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) {
|
export default async function RootLayout({ children, params }: { children: React.ReactNode , params:any}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,51 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Field, Form, Formik } from 'formik';
|
import { Field, Form, Formik } from 'formik';
|
||||||
import { updateOrganization } from '@services/settings/org';
|
import { updateOrganization, uploadOrganizationLogo } from '@services/settings/org';
|
||||||
|
import { UploadCloud } from 'lucide-react';
|
||||||
|
import { revalidateTags } from '@services/utils/ts/requests';
|
||||||
|
|
||||||
|
|
||||||
interface OrganizationValues {
|
interface OrganizationValues {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
logo: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function OrganizationClient(props: any) {
|
function OrganizationClient(props: any) {
|
||||||
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files && event.target.files.length > 0) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
setSelectedFile(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadLogo = async () => {
|
||||||
|
if (selectedFile) {
|
||||||
|
let org_id = org.org_id;
|
||||||
|
await uploadOrganizationLogo(org_id, selectedFile);
|
||||||
|
setSelectedFile(null); // Reset the selected file
|
||||||
|
revalidateTags(['organizations']);
|
||||||
|
// reload the page
|
||||||
|
// terrible hack, it will fixed later
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const org = props.org;
|
const org = props.org;
|
||||||
let orgValues: OrganizationValues = {
|
let orgValues: OrganizationValues = {
|
||||||
name: org.name,
|
name: org.name,
|
||||||
description: org.description,
|
description: org.description,
|
||||||
slug: org.slug,
|
slug: org.slug,
|
||||||
|
logo: org.logo,
|
||||||
email: org.email
|
email: org.email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,6 +54,7 @@ function OrganizationClient(props: any) {
|
||||||
await updateOrganization(org_id, values);
|
await updateOrganization(org_id, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className='text-3xl font-bold'>Organization Settings</h1>
|
<h1 className='text-3xl font-bold'>Organization Settings</h1>
|
||||||
|
|
@ -62,6 +91,28 @@ function OrganizationClient(props: any) {
|
||||||
name="description"
|
name="description"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<label className="block mb-2 font-bold" htmlFor="slug">
|
||||||
|
Logo
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-center w-full ">
|
||||||
|
<input
|
||||||
|
className="w-full px-4 py-2 mr-1 border rounded-lg bg-gray-200 cursor-not-allowed"
|
||||||
|
type="file"
|
||||||
|
name="logo"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={uploadLogo}
|
||||||
|
disabled={isSubmitting || selectedFile === null}
|
||||||
|
className="px-6 py-3 text-white bg-gray-500 rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-black"
|
||||||
|
>
|
||||||
|
<UploadCloud size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<label className="block mb-2 font-bold" htmlFor="slug">
|
<label className="block mb-2 font-bold" htmlFor="slug">
|
||||||
Slug
|
Slug
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
||||||
|
|
||||||
if (status.org_id == orgId) {
|
if (status.org_id == orgId) {
|
||||||
closeModal();
|
closeModal();
|
||||||
|
// reload the page
|
||||||
|
// terrible, nextjs right now doesn't mutate the page when the data changes
|
||||||
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
alert("Error creating course, please see console logs");
|
alert("Error creating course, please see console logs");
|
||||||
console.log(status);
|
console.log(status);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
'use client';
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { HeaderProfileBox } from "../../Security/HeaderProfileBox";
|
|
||||||
import learnhouseIcon from "public/learnhouse_icon.png";
|
|
||||||
import learnhouseLogo from "public/learnhouse_logo.png";
|
|
||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { getUriWithOrg } from "@services/config/config";
|
|
||||||
import ToolTip from "../Tooltip/Tooltip";
|
|
||||||
|
|
||||||
export const Menu = (props : any ) => {
|
|
||||||
const orgslug = props.orgslug;
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="h-[60px]"></div>
|
|
||||||
<GlobalHeader className="backdrop-blur-lg bg-white/90 fixed top-0 left-0 right-0">
|
|
||||||
<LogoArea>
|
|
||||||
<Logo>
|
|
||||||
<Image width={25} height={25} src={learnhouseIcon} alt="" />
|
|
||||||
<Link href={getUriWithOrg(orgslug, "/")}>
|
|
||||||
<Image width={108} height={28} src={learnhouseLogo} alt="" />
|
|
||||||
</Link>
|
|
||||||
</Logo>
|
|
||||||
<div id="accounts"></div>
|
|
||||||
</LogoArea>
|
|
||||||
<SearchArea>
|
|
||||||
<Search>
|
|
||||||
<ToolTip content={<div>
|
|
||||||
<p>{process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA}</p>
|
|
||||||
</div>}><PreAlphaLabel
|
|
||||||
className="opacity-90 outline-dashed outline-2 outline-orange-200 bg-orange-100 text-orange-500"
|
|
||||||
>pre-alpha</PreAlphaLabel></ToolTip>
|
|
||||||
</Search>
|
|
||||||
</SearchArea>
|
|
||||||
<MenuArea>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link href={getUriWithOrg(orgslug, "/courses")}>Courses</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href={getUriWithOrg(orgslug, "/collections")}>Collections</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{" "}
|
|
||||||
<Link href={getUriWithOrg(orgslug, "/trail")}>Trail</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</MenuArea>
|
|
||||||
<HeaderProfileBox></HeaderProfileBox>
|
|
||||||
</GlobalHeader></>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const GlobalHeader = styled.div`
|
|
||||||
display: flex;
|
|
||||||
height: 60px;
|
|
||||||
background: #ffffff;
|
|
||||||
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LogoArea = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: stretch;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PreAlphaLabel = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
height: 50%;
|
|
||||||
border: none;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bolder;
|
|
||||||
text-transform: uppercase;
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const Logo = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-left: 20px;
|
|
||||||
a {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchArea = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: stretch;
|
|
||||||
flex-grow: 2;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Search = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-left: 20px;
|
|
||||||
width: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const MenuArea = styled.div`
|
|
||||||
display: flex;
|
|
||||||
place-items: stretch;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 20px;
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding-right: 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #525252;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
105
front/components/UI/Elements/Menu/Menu.tsx
Normal file
105
front/components/UI/Elements/Menu/Menu.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import learnhouseLogo from "public/learnhouse_logo.png";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { getBackendUrl, getUriWithOrg } from "@services/config/config";
|
||||||
|
import { getOrganizationContextInfo, getOrganizationContextInfoNoAsync } from "@services/organizations/orgs";
|
||||||
|
import ClientComponentSkeleton from "@components/UI/Utils/ClientComp";
|
||||||
|
import { HeaderProfileBox } from "@components/Security/HeaderProfileBox";
|
||||||
|
|
||||||
|
export const Menu = async (props: any) => {
|
||||||
|
const orgslug = props.orgslug;
|
||||||
|
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
|
||||||
|
console.log(org);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="h-[60px]"></div>
|
||||||
|
<div className="backdrop-blur-lg bg-white/90 fixed flex top-0 left-0 right-0 h-[60px] items-center pl-5 pr-5 space-x-5 shadow-[0px_4px_16px_rgba(0,0,0,0.02)] z-50">
|
||||||
|
<div className="logo flex ">
|
||||||
|
<Link href={getUriWithOrg(orgslug, "/")}>
|
||||||
|
<div className="flex w-auto h-9 rounded-md items-center m-auto justify-center" >
|
||||||
|
{org?.logo ? (
|
||||||
|
<img
|
||||||
|
src={`${getBackendUrl()}content/uploads/logos/${org?.logo}`}
|
||||||
|
alt="Learnhouse"
|
||||||
|
style={{ width: "auto", height: "100%" }}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LearnHouseLogo></LearnHouseLogo>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="links flex grow">
|
||||||
|
<ul className="flex space-x-4">
|
||||||
|
<LinkItem link="/courses" type="courses" orgslug={orgslug}></LinkItem>
|
||||||
|
<LinkItem link="/collections" type="collections" orgslug={orgslug}></LinkItem>
|
||||||
|
<LinkItem link="/trail" type="trail" orgslug={orgslug}></LinkItem>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="profile flex">
|
||||||
|
<ClientComponentSkeleton>
|
||||||
|
<HeaderProfileBox />
|
||||||
|
</ClientComponentSkeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LinkItem = (props: any, orgslug: any) => {
|
||||||
|
const link = props.link;
|
||||||
|
return (
|
||||||
|
<Link href={getUriWithOrg(orgslug, link)}>
|
||||||
|
<li className="flex space-x-2 items-center text-[#909192] font-medium">
|
||||||
|
{props.type == 'courses' &&
|
||||||
|
<>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.9987 1.66663H6.66536C5.78131 1.66663 4.93346 2.01782 4.30834 2.64294C3.68322 3.26806 3.33203 4.1159 3.33203 4.99996V15C3.33203 15.884 3.68322 16.7319 4.30834 17.357C4.93346 17.9821 5.78131 18.3333 6.66536 18.3333H14.9987C15.4407 18.3333 15.8646 18.1577 16.1772 17.8451C16.4898 17.5326 16.6654 17.1087 16.6654 16.6666V3.33329C16.6654 2.89127 16.4898 2.46734 16.1772 2.15478C15.8646 1.84222 15.4407 1.66663 14.9987 1.66663ZM4.9987 4.99996C4.9987 4.55793 5.17429 4.13401 5.48685 3.82145C5.79941 3.50889 6.22334 3.33329 6.66536 3.33329H14.9987V11.6666H6.66536C6.0779 11.6691 5.50203 11.8303 4.9987 12.1333V4.99996ZM6.66536 16.6666C6.22334 16.6666 5.79941 16.491 5.48685 16.1785C5.17429 15.8659 4.9987 15.442 4.9987 15C4.9987 14.5579 5.17429 14.134 5.48685 13.8214C5.79941 13.5089 6.22334 13.3333 6.66536 13.3333H14.9987V16.6666H6.66536ZM8.33203 6.66663H11.6654C11.8864 6.66663 12.0983 6.57883 12.2546 6.42255C12.4109 6.26627 12.4987 6.05431 12.4987 5.83329C12.4987 5.61228 12.4109 5.40032 12.2546 5.24404C12.0983 5.08776 11.8864 4.99996 11.6654 4.99996H8.33203C8.11102 4.99996 7.89906 5.08776 7.74278 5.24404C7.5865 5.40032 7.4987 5.61228 7.4987 5.83329C7.4987 6.05431 7.5865 6.26627 7.74278 6.42255C7.89906 6.57883 8.11102 6.66663 8.33203 6.66663V6.66663Z" fill="#898A8B" />
|
||||||
|
</svg>
|
||||||
|
<span>Courses</span>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
{props.type == 'collections' &&
|
||||||
|
<>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.0567 6.14171C17.0567 6.14171 17.0567 6.14171 17.0567 6.07504L17.0067 5.95004C16.9893 5.92352 16.9698 5.89844 16.9483 5.87504C16.926 5.83976 16.901 5.80632 16.8733 5.77504L16.7983 5.71671L16.665 5.65004L10.415 1.79171C10.2826 1.70893 10.1295 1.66504 9.97333 1.66504C9.81715 1.66504 9.66411 1.70893 9.53166 1.79171L3.33166 5.65004L3.25666 5.71671L3.18166 5.77504C3.15404 5.80632 3.12896 5.83976 3.10666 5.87504C3.08524 5.89844 3.06573 5.92352 3.04833 5.95004L2.99833 6.07504C2.99833 6.07504 2.99833 6.07504 2.99833 6.14171C2.99014 6.2137 2.99014 6.28639 2.99833 6.35838V13.6417C2.99805 13.7833 3.03386 13.9227 3.10239 14.0466C3.17092 14.1706 3.2699 14.275 3.39 14.35L9.64 18.2084C9.67846 18.2321 9.72076 18.2491 9.765 18.2584C9.765 18.2584 9.80666 18.2584 9.83166 18.2584C9.97265 18.3031 10.124 18.3031 10.265 18.2584C10.265 18.2584 10.3067 18.2584 10.3317 18.2584C10.3759 18.2491 10.4182 18.2321 10.4567 18.2084L16.665 14.35C16.7851 14.275 16.8841 14.1706 16.9526 14.0466C17.0211 13.9227 17.0569 13.7833 17.0567 13.6417V6.35838C17.0649 6.28639 17.0649 6.2137 17.0567 6.14171ZM9.165 16.0084L4.58166 13.175V7.85838L9.165 10.6834V16.0084ZM9.99833 9.24171L5.33166 6.35838L9.99833 3.48337L14.665 6.35838L9.99833 9.24171ZM15.415 13.175L10.8317 16.0084V10.6834L15.415 7.85838V13.175Z" fill="#898A8B" />
|
||||||
|
</svg>
|
||||||
|
<span>Collections</span>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
{props.type == 'trail' &&
|
||||||
|
<>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16.5751 7.95841C16.5059 7.82098 16.3999 7.70541 16.269 7.62451C16.1381 7.54361 15.9874 7.50054 15.8335 7.50008H11.6668V2.50008C11.6757 2.31731 11.6243 2.13669 11.5204 1.98608C11.4164 1.83547 11.2658 1.72325 11.0918 1.66674C10.9245 1.6117 10.744 1.61108 10.5763 1.66498C10.4087 1.71888 10.2624 1.82452 10.1585 1.96674L3.4918 11.1334C3.40827 11.2541 3.35811 11.3948 3.3464 11.5411C3.3347 11.6874 3.36186 11.8343 3.42513 11.9667C3.4834 12.1182 3.58462 12.2493 3.71637 12.3441C3.84812 12.4388 4.00467 12.493 4.1668 12.5001H8.33346V17.5001C8.33359 17.6758 8.38927 17.847 8.49254 17.9892C8.59581 18.1314 8.74139 18.2373 8.90846 18.2917C8.99219 18.3177 9.07915 18.3317 9.1668 18.3334C9.29828 18.3338 9.42799 18.303 9.5453 18.2436C9.66262 18.1842 9.76422 18.0979 9.8418 17.9917L16.5085 8.82508C16.5982 8.70074 16.652 8.55404 16.6637 8.40112C16.6755 8.24821 16.6448 8.09502 16.5751 7.95841ZM10.0001 14.9334V11.6667C10.0001 11.4457 9.91233 11.2338 9.75605 11.0775C9.59977 10.9212 9.38781 10.8334 9.1668 10.8334H5.83346L10.0001 5.06674V8.33341C10.0001 8.55442 10.0879 8.76638 10.2442 8.92267C10.4005 9.07895 10.6124 9.16674 10.8335 9.16674H14.1668L10.0001 14.9334Z" fill="#909192" />
|
||||||
|
</svg>
|
||||||
|
<span>Trail</span>
|
||||||
|
</>}
|
||||||
|
</li>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const LearnHouseLogo = () => {
|
||||||
|
return (
|
||||||
|
<svg width="133" height="80" viewBox="0 0 433 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="80" height="80" rx="24" fill="black" />
|
||||||
|
<rect width="80" height="80" rx="24" fill="url(#paint0_angular_1555_220)" />
|
||||||
|
<rect x="0.5" y="0.5" width="79" height="79" rx="23.5" stroke="white" stroke-opacity="0.12" />
|
||||||
|
<path d="M37.546 55.926V35.04L33.534 30.497L37.546 29.258V27.016L33.534 22.473L44.626 19.11V55.926L48.992 61H33.18L37.546 55.926Z" fill="white" />
|
||||||
|
<path d="M113.98 54.98V30.2L109.22 24.81L113.98 23.34V20.68L109.22 15.29L122.38 11.3V54.98L127.56 61H108.8L113.98 54.98ZM157.704 41.19V41.26H135.234C136.004 50.29 140.834 54.07 146.294 54.07C151.054 54.07 155.254 51.69 156.304 48.75L157.354 49.17C154.834 55.54 149.864 61.98 141.534 61.98C132.364 61.98 127.184 53.79 127.184 45.39C127.184 36.36 132.784 26 144.194 26C152.524 26 157.634 31.6 157.704 41.05L157.774 41.19H157.704ZM148.674 39.16V38.53C148.674 31.04 145.664 28.1 142.584 28.1C137.264 28.1 135.094 34.47 135.094 38.67V39.16H148.674ZM178.717 61V55.12C176.057 57.71 171.157 61.7 166.537 61.7C161.707 61.7 158.137 59.32 158.137 53.65C158.137 46.51 166.607 42.87 178.717 38.6C178.717 33 178.577 28.66 172.837 28.66C167.237 28.66 163.877 32.58 160.307 37.9H159.817V26.7H188.657L187.117 32.72V56.45H187.187L192.367 61H178.717ZM178.717 53.23V40.56C167.727 44.97 167.377 47.98 167.377 51.34C167.377 54.7 169.687 56.17 172.627 56.17C174.797 56.17 176.967 55.05 178.717 53.23ZM221.429 39.09H220.869C217.789 31.74 213.659 29.29 210.439 29.29C205.609 29.29 205.609 32.79 205.609 39.93V54.98L212.119 61H192.029L197.209 54.98V32.09L192.449 26.7H221.429V39.09ZM261.467 61H242.707L247.747 54.98V39.44C247.747 34.05 246.977 30.62 241.587 30.62C238.997 30.62 236.337 31.74 234.097 34.75V54.98L239.137 61H220.377L225.697 54.98V36.08L220.937 30.69L234.097 26V32.37C236.897 28.03 241.447 25.86 245.647 25.86C252.787 25.86 256.147 30.48 256.147 37.06V54.98L261.467 61ZM274.343 11.3V32.23C277.143 27.89 281.693 25.72 285.893 25.72C293.033 25.72 296.393 30.34 296.393 36.92V54.98H296.463L301.643 61H282.883L287.993 55.05V39.3C287.993 33.91 287.223 30.48 281.833 30.48C279.243 30.48 276.583 31.6 274.343 34.61V54.98L279.523 61H260.763L265.943 54.98V21.38L261.183 15.99L274.343 11.3ZM335.945 42.31C335.945 51.34 329.855 61.84 316.835 61.84C306.895 61.84 301.645 53.79 301.645 45.39C301.645 36.36 307.735 25.86 320.755 25.86C330.695 25.86 335.945 33.91 335.945 42.31ZM316.975 28.52C311.165 28.52 310.535 34.82 310.535 39.02C310.535 49.94 314.525 59.18 320.685 59.18C325.865 59.18 327.195 52.32 327.195 48.68C327.195 37.76 323.135 28.52 316.975 28.52ZM349.01 26.63V48.12C349.01 53.51 349.78 56.94 355.17 56.94C357.55 56.94 360 55.75 361.82 53.65V32.72L356.64 26.63H370.22V55.26L374.98 61L361.82 61.42V55.82C359.3 59.32 356.08 61.7 351.11 61.7C343.97 61.7 340.61 57.08 340.61 50.5V32.72L335.36 26.63H349.01ZM374.617 47.77H375.177C376.997 53.79 382.527 59.04 388.267 59.04C391.137 59.04 393.517 57.64 393.517 54.49C393.517 46.23 374.967 50.29 374.967 36.43C374.967 31.25 379.517 26.7 386.657 26.7H394.357L396.947 25.23V36.85L396.527 36.78C394.007 32.23 389.807 28.87 385.327 28.94C382.387 29.01 380.707 30.83 380.707 33.56C380.707 40.77 399.887 37.62 399.887 50.43C399.887 58.55 391.697 61.7 386.167 61.7C382.667 61.7 377.907 61.21 375.247 60.09L374.617 47.77ZM430.416 41.19V41.26H407.946C408.716 50.29 413.546 54.07 419.006 54.07C423.766 54.07 427.966 51.69 429.016 48.75L430.066 49.17C427.546 55.54 422.576 61.98 414.246 61.98C405.076 61.98 399.896 53.79 399.896 45.39C399.896 36.36 405.496 26 416.906 26C425.236 26 430.346 31.6 430.416 41.05L430.486 41.19H430.416ZM421.386 39.16V38.53C421.386 31.04 418.376 28.1 415.296 28.1C409.976 28.1 407.806 34.47 407.806 38.67V39.16H421.386Z" fill="#121212" />
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="paint0_angular_1555_220" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 40) rotate(90) scale(40)">
|
||||||
|
<stop stop-color="#FBFBFB" stop-opacity="0.15" />
|
||||||
|
<stop offset="0.442708" stop-opacity="0.1" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
156
front/components/UI/Elements/Menu/ProfileArea.tsx
Normal file
156
front/components/UI/Elements/Menu/ProfileArea.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Avvvatars from "avvvatars-react";
|
||||||
|
import { GearIcon } from "@radix-ui/react-icons";
|
||||||
|
import { getRefreshToken, getUserInfo } from "@services/auth/auth";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
export interface Auth {
|
||||||
|
access_token: string;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
userInfo: any;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfileArea() {
|
||||||
|
|
||||||
|
|
||||||
|
const PRIVATE_ROUTES = ["/course/*/edit", "/settings*", "/trail"];
|
||||||
|
const NON_AUTHENTICATED_ROUTES = ["/login", "/register"];
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [auth, setAuth] = React.useState<Auth>({ access_token: "", isAuthenticated: false, userInfo: {}, isLoading: true });
|
||||||
|
|
||||||
|
async function checkRefreshToken() {
|
||||||
|
let data = await getRefreshToken();
|
||||||
|
if (data) {
|
||||||
|
return data.access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function checkAuth() {
|
||||||
|
try {
|
||||||
|
let access_token = await checkRefreshToken();
|
||||||
|
let userInfo = {};
|
||||||
|
let isLoading = false;
|
||||||
|
|
||||||
|
if (access_token) {
|
||||||
|
userInfo = await getUserInfo(access_token);
|
||||||
|
setAuth({ access_token, isAuthenticated: true, userInfo, isLoading });
|
||||||
|
|
||||||
|
// Redirect to home if user is trying to access a NON_AUTHENTICATED_ROUTES route
|
||||||
|
|
||||||
|
if (NON_AUTHENTICATED_ROUTES.some((route) => new RegExp(`^${route.replace("*", ".*")}$`).test(pathname))) {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setAuth({ access_token, isAuthenticated: false, userInfo, isLoading });
|
||||||
|
|
||||||
|
// Redirect to login if user is trying to access a private route
|
||||||
|
if (PRIVATE_ROUTES.some((route) => new RegExp(`^${route.replace("*", ".*")}$`).test(pathname))) {
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ProfileAreaStyled>
|
||||||
|
{!auth.isAuthenticated && (
|
||||||
|
<UnidentifiedArea>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link href="/login">
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/signup">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</UnidentifiedArea>
|
||||||
|
)}
|
||||||
|
{auth.isAuthenticated && (
|
||||||
|
<AccountArea>
|
||||||
|
<div>{auth.userInfo.user_object.username}</div>
|
||||||
|
<div>
|
||||||
|
<Avvvatars value={auth.userInfo.user_object.user_id} style="shape" />
|
||||||
|
</div>
|
||||||
|
<Link href={"/settings"}><GearIcon /></Link>
|
||||||
|
</AccountArea>
|
||||||
|
)}
|
||||||
|
</ProfileAreaStyled>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccountArea = styled.div`
|
||||||
|
padding-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
a{
|
||||||
|
// center the gear icon
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
width: 29px;
|
||||||
|
height: 29px;
|
||||||
|
border-radius: 19px;
|
||||||
|
background: #F5F5F5;
|
||||||
|
|
||||||
|
// hover effect
|
||||||
|
&:hover{
|
||||||
|
background: #E5E5E5;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 29px;
|
||||||
|
border-radius: 19px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ProfileAreaStyled = styled.div`
|
||||||
|
display: flex;
|
||||||
|
place-items: stretch;
|
||||||
|
place-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UnidentifiedArea = styled.div`
|
||||||
|
display: flex;
|
||||||
|
place-items: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-right: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #171717;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
export default ProfileArea
|
||||||
13
front/components/UI/Utils/ClientComp.tsx
Normal file
13
front/components/UI/Utils/ClientComp.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
function ClientComponentSkeleton({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>{children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClientComponentSkeleton
|
||||||
|
|
@ -25,7 +25,7 @@ export default function middleware(req: NextRequest) {
|
||||||
|
|
||||||
// Organizations & Global settings
|
// Organizations & Global settings
|
||||||
if (pathname.startsWith("/organizations")) {
|
if (pathname.startsWith("/organizations")) {
|
||||||
return NextResponse.rewrite(new URL("/organizations", req.url));
|
return NextResponse.rewrite(new URL(pathname, req.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic Pages Editor
|
// Dynamic Pages Editor
|
||||||
|
|
|
||||||
217
front/package-lock.json
generated
217
front/package-lock.json
generated
|
|
@ -26,7 +26,7 @@
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"framer-motion": "^7.3.6",
|
"framer-motion": "^7.3.6",
|
||||||
"lucide-react": "^0.104.1",
|
"lucide-react": "^0.104.1",
|
||||||
"next": "^13.4.3",
|
"next": "^13.4.6",
|
||||||
"re-resizable": "^6.9.9",
|
"re-resizable": "^6.9.9",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
@ -2123,9 +2123,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.6.tgz",
|
||||||
"integrity": "sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ=="
|
"integrity": "sha512-nqUxEtvDqFhmV1/awSg0K2XHNwkftNaiUqCYO9e6+MYmqNObpKVl7OgMkGaQ2SZnFx5YqF0t60ZJTlyJIDAijg=="
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "13.0.6",
|
"version": "13.0.6",
|
||||||
|
|
@ -2137,9 +2137,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.6.tgz",
|
||||||
"integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==",
|
"integrity": "sha512-ahi6VP98o4HV19rkOXPSUu+ovfHfUxbJQ7VVJ7gL2FnZRr7onEFC1oGQ6NQHpm8CxpIzSSBW79kumlFMOmZVjg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2152,9 +2152,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.6.tgz",
|
||||||
"integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==",
|
"integrity": "sha512-13cXxKFsPJIJKzUqrU5XB1mc0xbUgYsRcdH6/rB8c4NMEbWGdtD4QoK9ShN31TZdePpD4k416Ur7p+deMIxnnA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2167,9 +2167,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.6.tgz",
|
||||||
"integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==",
|
"integrity": "sha512-Ti+NMHEjTNktCVxNjeWbYgmZvA2AqMMI2AMlzkXsU7W4pXCMhrryAmAIoo+7YdJbsx01JQWYVxGe62G6DoCLaA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2182,9 +2182,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.6.tgz",
|
||||||
"integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==",
|
"integrity": "sha512-OHoC6gO7XfjstgwR+z6UHKlvhqJfyMtNaJidjx3sEcfaDwS7R2lqR5AABi8PuilGgi0BO0O0sCXqLlpp3a0emQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2197,9 +2197,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.6.tgz",
|
||||||
"integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==",
|
"integrity": "sha512-zHZxPGkUlpfNJCboUrFqwlwEX5vI9LSN70b8XEb0DYzzlrZyCyOi7hwDp/+3Urm9AB7YCAJkgR5Sp1XBVjHdfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2212,9 +2212,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.6.tgz",
|
||||||
"integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==",
|
"integrity": "sha512-K/Y8lYGTwTpv5ME8PSJxwxLolaDRdVy+lOd9yMRMiQE0BLUhtxtCWC9ypV42uh9WpLjoaD0joOsB9Q6mbrSGJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -2227,9 +2227,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==",
|
"integrity": "sha512-U6LtxEUrjBL2tpW+Kr1nHCSJWNeIed7U7l5o7FiKGGwGgIlFi4UHDiLI6TQ2lxi20fAU33CsruV3U0GuzMlXIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -2242,9 +2242,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==",
|
"integrity": "sha512-eEBeAqpCfhdPSlCZCayjCiyIllVqy4tcqvm1xmg3BgJG0G5ITiMM4Cw2WVeRSgWDJqQGRyyb+q8Y2ltzhXOWsQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -2257,9 +2257,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==",
|
"integrity": "sha512-OrZs94AuO3ZS5tnqlyPRNgfWvboXaDQCi5aXGve3o3C+Sj0ctMUV9+Do+0zMvvLRumR8E0PTWKvtz9n5vzIsWw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5469,6 +5469,11 @@
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob-to-regexp": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
|
||||||
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "13.17.0",
|
"version": "13.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||||
|
|
@ -5539,8 +5544,7 @@
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.10",
|
"version": "4.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/grapheme-splitter": {
|
"node_modules/grapheme-splitter": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
|
@ -6371,16 +6375,17 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-13.4.6.tgz",
|
||||||
"integrity": "sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==",
|
"integrity": "sha512-sjVqjxU+U2aXZnYt4Ud6CTLNNwWjdSfMgemGpIQJcN3Z7Jni9xRWbR0ie5fQzCg87aLqQVhKA2ud2gPoqJ9lGw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "13.4.3",
|
"@next/env": "13.4.6",
|
||||||
"@swc/helpers": "0.5.1",
|
"@swc/helpers": "0.5.1",
|
||||||
"busboy": "1.6.0",
|
"busboy": "1.6.0",
|
||||||
"caniuse-lite": "^1.0.30001406",
|
"caniuse-lite": "^1.0.30001406",
|
||||||
"postcss": "8.4.14",
|
"postcss": "8.4.14",
|
||||||
"styled-jsx": "5.1.1",
|
"styled-jsx": "5.1.1",
|
||||||
|
"watchpack": "2.4.0",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -6390,20 +6395,19 @@
|
||||||
"node": ">=16.8.0"
|
"node": ">=16.8.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "13.4.3",
|
"@next/swc-darwin-arm64": "13.4.6",
|
||||||
"@next/swc-darwin-x64": "13.4.3",
|
"@next/swc-darwin-x64": "13.4.6",
|
||||||
"@next/swc-linux-arm64-gnu": "13.4.3",
|
"@next/swc-linux-arm64-gnu": "13.4.6",
|
||||||
"@next/swc-linux-arm64-musl": "13.4.3",
|
"@next/swc-linux-arm64-musl": "13.4.6",
|
||||||
"@next/swc-linux-x64-gnu": "13.4.3",
|
"@next/swc-linux-x64-gnu": "13.4.6",
|
||||||
"@next/swc-linux-x64-musl": "13.4.3",
|
"@next/swc-linux-x64-musl": "13.4.6",
|
||||||
"@next/swc-win32-arm64-msvc": "13.4.3",
|
"@next/swc-win32-arm64-msvc": "13.4.6",
|
||||||
"@next/swc-win32-ia32-msvc": "13.4.3",
|
"@next/swc-win32-ia32-msvc": "13.4.6",
|
||||||
"@next/swc-win32-x64-msvc": "13.4.3"
|
"@next/swc-win32-x64-msvc": "13.4.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
"fibers": ">= 3.1.0",
|
"fibers": ">= 3.1.0",
|
||||||
"node-sass": "^6.0.0 || ^7.0.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sass": "^1.3.0"
|
"sass": "^1.3.0"
|
||||||
|
|
@ -6415,9 +6419,6 @@
|
||||||
"fibers": {
|
"fibers": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node-sass": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
|
|
@ -8369,6 +8370,18 @@
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
||||||
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
|
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/watchpack": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
|
||||||
|
"dependencies": {
|
||||||
|
"glob-to-regexp": "^0.4.1",
|
||||||
|
"graceful-fs": "^4.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|
@ -10043,9 +10056,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@next/env": {
|
"@next/env": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.6.tgz",
|
||||||
"integrity": "sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ=="
|
"integrity": "sha512-nqUxEtvDqFhmV1/awSg0K2XHNwkftNaiUqCYO9e6+MYmqNObpKVl7OgMkGaQ2SZnFx5YqF0t60ZJTlyJIDAijg=="
|
||||||
},
|
},
|
||||||
"@next/eslint-plugin-next": {
|
"@next/eslint-plugin-next": {
|
||||||
"version": "13.0.6",
|
"version": "13.0.6",
|
||||||
|
|
@ -10057,57 +10070,57 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@next/swc-darwin-arm64": {
|
"@next/swc-darwin-arm64": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.6.tgz",
|
||||||
"integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==",
|
"integrity": "sha512-ahi6VP98o4HV19rkOXPSUu+ovfHfUxbJQ7VVJ7gL2FnZRr7onEFC1oGQ6NQHpm8CxpIzSSBW79kumlFMOmZVjg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-darwin-x64": {
|
"@next/swc-darwin-x64": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.6.tgz",
|
||||||
"integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==",
|
"integrity": "sha512-13cXxKFsPJIJKzUqrU5XB1mc0xbUgYsRcdH6/rB8c4NMEbWGdtD4QoK9ShN31TZdePpD4k416Ur7p+deMIxnnA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-linux-arm64-gnu": {
|
"@next/swc-linux-arm64-gnu": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.6.tgz",
|
||||||
"integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==",
|
"integrity": "sha512-Ti+NMHEjTNktCVxNjeWbYgmZvA2AqMMI2AMlzkXsU7W4pXCMhrryAmAIoo+7YdJbsx01JQWYVxGe62G6DoCLaA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-linux-arm64-musl": {
|
"@next/swc-linux-arm64-musl": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.6.tgz",
|
||||||
"integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==",
|
"integrity": "sha512-OHoC6gO7XfjstgwR+z6UHKlvhqJfyMtNaJidjx3sEcfaDwS7R2lqR5AABi8PuilGgi0BO0O0sCXqLlpp3a0emQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-linux-x64-gnu": {
|
"@next/swc-linux-x64-gnu": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.6.tgz",
|
||||||
"integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==",
|
"integrity": "sha512-zHZxPGkUlpfNJCboUrFqwlwEX5vI9LSN70b8XEb0DYzzlrZyCyOi7hwDp/+3Urm9AB7YCAJkgR5Sp1XBVjHdfQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-linux-x64-musl": {
|
"@next/swc-linux-x64-musl": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.6.tgz",
|
||||||
"integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==",
|
"integrity": "sha512-K/Y8lYGTwTpv5ME8PSJxwxLolaDRdVy+lOd9yMRMiQE0BLUhtxtCWC9ypV42uh9WpLjoaD0joOsB9Q6mbrSGJg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-win32-arm64-msvc": {
|
"@next/swc-win32-arm64-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==",
|
"integrity": "sha512-U6LtxEUrjBL2tpW+Kr1nHCSJWNeIed7U7l5o7FiKGGwGgIlFi4UHDiLI6TQ2lxi20fAU33CsruV3U0GuzMlXIw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-win32-ia32-msvc": {
|
"@next/swc-win32-ia32-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==",
|
"integrity": "sha512-eEBeAqpCfhdPSlCZCayjCiyIllVqy4tcqvm1xmg3BgJG0G5ITiMM4Cw2WVeRSgWDJqQGRyyb+q8Y2ltzhXOWsQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@next/swc-win32-x64-msvc": {
|
"@next/swc-win32-x64-msvc": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.6.tgz",
|
||||||
"integrity": "sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==",
|
"integrity": "sha512-OrZs94AuO3ZS5tnqlyPRNgfWvboXaDQCi5aXGve3o3C+Sj0ctMUV9+Do+0zMvvLRumR8E0PTWKvtz9n5vzIsWw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@nicolo-ribaudo/chokidar-2": {
|
"@nicolo-ribaudo/chokidar-2": {
|
||||||
|
|
@ -12423,6 +12436,11 @@
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"glob-to-regexp": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
|
||||||
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"version": "13.17.0",
|
"version": "13.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||||
|
|
@ -12476,8 +12494,7 @@
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
"version": "4.2.10",
|
"version": "4.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"grapheme-splitter": {
|
"grapheme-splitter": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
|
@ -13076,25 +13093,26 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"next": {
|
"next": {
|
||||||
"version": "13.4.3",
|
"version": "13.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-13.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-13.4.6.tgz",
|
||||||
"integrity": "sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==",
|
"integrity": "sha512-sjVqjxU+U2aXZnYt4Ud6CTLNNwWjdSfMgemGpIQJcN3Z7Jni9xRWbR0ie5fQzCg87aLqQVhKA2ud2gPoqJ9lGw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@next/env": "13.4.3",
|
"@next/env": "13.4.6",
|
||||||
"@next/swc-darwin-arm64": "13.4.3",
|
"@next/swc-darwin-arm64": "13.4.6",
|
||||||
"@next/swc-darwin-x64": "13.4.3",
|
"@next/swc-darwin-x64": "13.4.6",
|
||||||
"@next/swc-linux-arm64-gnu": "13.4.3",
|
"@next/swc-linux-arm64-gnu": "13.4.6",
|
||||||
"@next/swc-linux-arm64-musl": "13.4.3",
|
"@next/swc-linux-arm64-musl": "13.4.6",
|
||||||
"@next/swc-linux-x64-gnu": "13.4.3",
|
"@next/swc-linux-x64-gnu": "13.4.6",
|
||||||
"@next/swc-linux-x64-musl": "13.4.3",
|
"@next/swc-linux-x64-musl": "13.4.6",
|
||||||
"@next/swc-win32-arm64-msvc": "13.4.3",
|
"@next/swc-win32-arm64-msvc": "13.4.6",
|
||||||
"@next/swc-win32-ia32-msvc": "13.4.3",
|
"@next/swc-win32-ia32-msvc": "13.4.6",
|
||||||
"@next/swc-win32-x64-msvc": "13.4.3",
|
"@next/swc-win32-x64-msvc": "13.4.6",
|
||||||
"@swc/helpers": "0.5.1",
|
"@swc/helpers": "0.5.1",
|
||||||
"busboy": "1.6.0",
|
"busboy": "1.6.0",
|
||||||
"caniuse-lite": "^1.0.30001406",
|
"caniuse-lite": "^1.0.30001406",
|
||||||
"postcss": "8.4.14",
|
"postcss": "8.4.14",
|
||||||
"styled-jsx": "5.1.1",
|
"styled-jsx": "5.1.1",
|
||||||
|
"watchpack": "2.4.0",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -14432,6 +14450,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
||||||
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
|
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
|
||||||
},
|
},
|
||||||
|
"watchpack": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
|
||||||
|
"requires": {
|
||||||
|
"glob-to-regexp": "^0.4.1",
|
||||||
|
"graceful-fs": "^4.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"framer-motion": "^7.3.6",
|
"framer-motion": "^7.3.6",
|
||||||
"lucide-react": "^0.104.1",
|
"lucide-react": "^0.104.1",
|
||||||
"next": "^13.4.3",
|
"next": "^13.4.6",
|
||||||
"re-resizable": "^6.9.9",
|
"re-resizable": "^6.9.9",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,8 @@ export async function getOrganizationContextInfo(org_slug: any, next: any) {
|
||||||
const res = await errorHandling(result);
|
const res = await errorHandling(result);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOrganizationContextInfoNoAsync(org_slug: any, next: any) {
|
||||||
|
const result = fetch(`${getAPIUrl()}orgs/slug/${org_slug}`, RequestBody("GET", null, next));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { getAPIUrl } from "@services/config/config";
|
import { getAPIUrl } from "@services/config/config";
|
||||||
import { RequestBody, errorHandling } from "@services/utils/ts/requests";
|
import { RequestBody, errorHandling, RequestBodyForm } from "@services/utils/ts/requests";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file includes only POST, PUT, DELETE requests
|
This file includes only POST, PUT, DELETE requests
|
||||||
|
|
@ -11,3 +11,12 @@ export async function updateOrganization(org_id: string, data: any) {
|
||||||
const res = await errorHandling(result);
|
const res = await errorHandling(result);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function uploadOrganizationLogo(org_id: string, logo_file: any) {
|
||||||
|
// Send file thumbnail as form data
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("logo_file", logo_file);
|
||||||
|
const result: any = await fetch(`${getAPIUrl()}orgs/` + org_id + "/logo", RequestBodyForm("PUT", formData, null));
|
||||||
|
const res = await errorHandling(result);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request, UploadFile
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org
|
from src.services.orgs.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org, update_org_logo
|
||||||
from src.services.users.users import PublicUser, User
|
from src.services.users.users import PublicUser, User
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,6 +31,12 @@ async def api_get_org_by_slug(request: Request, org_slug: str, current_user: Use
|
||||||
"""
|
"""
|
||||||
return await get_organization_by_slug(request, org_slug)
|
return await get_organization_by_slug(request, org_slug)
|
||||||
|
|
||||||
|
@router.put("/{org_id}/logo")
|
||||||
|
async def api_update_org_logo(request: Request, org_id: str, logo_file:UploadFile, current_user: PublicUser = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Get single Org by Slug
|
||||||
|
"""
|
||||||
|
return await update_org_logo(request=request,logo_file=logo_file, org_id=org_id, current_user=current_user)
|
||||||
|
|
||||||
@router.get("/user/page/{page}/limit/{limit}")
|
@router.get("/user/page/{page}/limit/{limit}")
|
||||||
async def api_user_orgs(request: Request, page: int, limit: int, current_user: PublicUser = Depends(get_current_user)):
|
async def api_user_orgs(request: Request, page: int, limit: int, current_user: PublicUser = Depends(get_current_user)):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from passlib.context import CryptContext
|
||||||
from passlib.hash import pbkdf2_sha256
|
from passlib.hash import pbkdf2_sha256
|
||||||
from src.services.roles.schemas.roles import RoleInDB
|
from src.services.roles.schemas.roles import RoleInDB
|
||||||
|
|
||||||
from src.services.users.schemas.users import UserInDB
|
from src.services.users.schemas.users import UserInDB, UserRolesInOrganization
|
||||||
|
|
||||||
### 🔒 JWT ##############################################################
|
### 🔒 JWT ##############################################################
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ async def check_element_type(element_id):
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Issue verifying element nature")
|
status_code=status.HTTP_409_CONFLICT, detail="Issue verifying element nature")
|
||||||
|
|
||||||
|
|
||||||
async def check_user_role_org_with_element_org(request: Request, element_id: str, roles_list: list[str]):
|
async def check_user_role_org_with_element_org(request: Request, element_id: str, roles_list: list[UserRolesInOrganization]):
|
||||||
|
|
||||||
element_type = await check_element_type(element_id)
|
element_type = await check_element_type(element_id)
|
||||||
element = request.app.db[element_type]
|
element = request.app.db[element_type]
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,5 @@ async def upload_file_and_return_file_object(request: Request, file: UploadFile,
|
||||||
f.write(file_binary)
|
f.write(file_binary)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
# TODO: do some error handling here
|
|
||||||
|
|
||||||
return uploadable_file
|
return uploadable_file
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class CourseChapterInDB(CourseChapter):
|
||||||
creationDate: str
|
creationDate: str
|
||||||
updateDate: str
|
updateDate: str
|
||||||
|
|
||||||
|
|
||||||
#### Classes ####################################################
|
#### Classes ####################################################
|
||||||
|
|
||||||
# TODO : Add courses photo & cover upload and delete
|
# TODO : Add courses photo & cover upload and delete
|
||||||
|
|
@ -55,6 +56,7 @@ class CourseChapterInDB(CourseChapter):
|
||||||
# CRUD
|
# CRUD
|
||||||
####################################################
|
####################################################
|
||||||
|
|
||||||
|
|
||||||
async def get_course(request: Request, course_id: str, current_user: PublicUser):
|
async def get_course(request: Request, course_id: str, current_user: PublicUser):
|
||||||
courses = request.app.db["courses"]
|
courses = request.app.db["courses"]
|
||||||
|
|
||||||
|
|
@ -65,7 +67,8 @@ async def get_course(request: Request, course_id: str, current_user: PublicUser)
|
||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
course = Course(**course)
|
course = Course(**course)
|
||||||
return course
|
return course
|
||||||
|
|
@ -83,10 +86,12 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
|
||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
coursechapters = await courses.find_one({"course_id": course_id}, {
|
coursechapters = await courses.find_one(
|
||||||
"chapters_content": 1, "_id": 0})
|
{"course_id": course_id}, {"chapters_content": 1, "_id": 0}
|
||||||
|
)
|
||||||
|
|
||||||
# activities
|
# activities
|
||||||
coursechapter_activityIds_global = []
|
coursechapter_activityIds_global = []
|
||||||
|
|
@ -103,42 +108,66 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
|
||||||
coursechapter_activityIds_global.append(activity)
|
coursechapter_activityIds_global.append(activity)
|
||||||
|
|
||||||
chapters[coursechapter.coursechapter_id] = {
|
chapters[coursechapter.coursechapter_id] = {
|
||||||
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "activityIds": coursechapter_activityIds
|
"id": coursechapter.coursechapter_id,
|
||||||
|
"name": coursechapter.name,
|
||||||
|
"activityIds": coursechapter_activityIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
# activities
|
# activities
|
||||||
activities_list = {}
|
activities_list = {}
|
||||||
for activity in await activities.find({"activity_id": {"$in": coursechapter_activityIds_global}}).to_list(length=100):
|
for activity in await activities.find(
|
||||||
|
{"activity_id": {"$in": coursechapter_activityIds_global}}
|
||||||
|
).to_list(length=100):
|
||||||
activity = ActivityInDB(**activity)
|
activity = ActivityInDB(**activity)
|
||||||
activities_list[activity.activity_id] = {
|
activities_list[activity.activity_id] = {
|
||||||
"id": activity.activity_id, "name": activity.name, "type": activity.type, "content": activity.content
|
"id": activity.activity_id,
|
||||||
|
"name": activity.name,
|
||||||
|
"type": activity.type,
|
||||||
|
"content": activity.content,
|
||||||
}
|
}
|
||||||
|
|
||||||
chapters_list_with_activities = []
|
chapters_list_with_activities = []
|
||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
chapters_list_with_activities.append(
|
chapters_list_with_activities.append(
|
||||||
{"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "activities": [activities_list[activity] for activity in chapters[chapter]["activityIds"]]})
|
{
|
||||||
|
"id": chapters[chapter]["id"],
|
||||||
|
"name": chapters[chapter]["name"],
|
||||||
|
"activities": [
|
||||||
|
activities_list[activity]
|
||||||
|
for activity in chapters[chapter]["activityIds"]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
course = CourseInDB(**course)
|
course = CourseInDB(**course)
|
||||||
|
|
||||||
# Get activity by user
|
# Get activity by user
|
||||||
trail = await trails.find_one(
|
trail = await trails.find_one(
|
||||||
{"courses.course_id": course_id, "user_id": current_user.user_id})
|
{"courses.course_id": course_id, "user_id": current_user.user_id}
|
||||||
|
)
|
||||||
print(trail)
|
print(trail)
|
||||||
if trail:
|
if trail:
|
||||||
# get only the course where course_id == course_id
|
# get only the course where course_id == course_id
|
||||||
trail_course = next(
|
trail_course = next(
|
||||||
(course for course in trail["courses"] if course["course_id"] == course_id), None)
|
(course for course in trail["courses"] if course["course_id"] == course_id),
|
||||||
|
None,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
trail_course = ""
|
trail_course = ""
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"course": course,
|
"course": course,
|
||||||
"chapters": chapters_list_with_activities,
|
"chapters": chapters_list_with_activities,
|
||||||
"trail": trail_course
|
"trail": trail_course,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def create_course(request: Request, course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
|
async def create_course(
|
||||||
|
request: Request,
|
||||||
|
course_object: Course,
|
||||||
|
org_id: str,
|
||||||
|
current_user: PublicUser,
|
||||||
|
thumbnail_file: UploadFile | None = None,
|
||||||
|
):
|
||||||
courses = request.app.db["courses"]
|
courses = request.app.db["courses"]
|
||||||
|
|
||||||
# generate course_id with uuid4
|
# generate course_id with uuid4
|
||||||
|
|
@ -147,27 +176,42 @@ async def create_course(request: Request, course_object: Course, org_id: str, cu
|
||||||
# TODO(fix) : the implementation here is clearly not the best one (this entire function)
|
# TODO(fix) : the implementation here is clearly not the best one (this entire function)
|
||||||
course_object.org_id = org_id
|
course_object.org_id = org_id
|
||||||
course_object.chapters_content = []
|
course_object.chapters_content = []
|
||||||
await verify_user_rights_with_roles(request, "create", current_user.user_id, course_id, org_id)
|
await verify_user_rights_with_roles(
|
||||||
|
request, "create", current_user.user_id, course_id, org_id
|
||||||
|
)
|
||||||
|
|
||||||
if thumbnail_file:
|
if thumbnail_file and thumbnail_file.filename:
|
||||||
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
name_in_disk = (
|
||||||
|
f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||||
|
)
|
||||||
await upload_thumbnail(thumbnail_file, name_in_disk)
|
await upload_thumbnail(thumbnail_file, name_in_disk)
|
||||||
course_object.thumbnail = name_in_disk
|
course_object.thumbnail = name_in_disk
|
||||||
|
|
||||||
course = CourseInDB(course_id=course_id, authors=[
|
course = CourseInDB(
|
||||||
current_user.user_id], creationDate=str(datetime.now()), updateDate=str(datetime.now()), **course_object.dict())
|
course_id=course_id,
|
||||||
|
authors=[current_user.user_id],
|
||||||
|
creationDate=str(datetime.now()),
|
||||||
|
updateDate=str(datetime.now()),
|
||||||
|
**course_object.dict(),
|
||||||
|
)
|
||||||
|
|
||||||
course_in_db = await courses.insert_one(course.dict())
|
course_in_db = await courses.insert_one(course.dict())
|
||||||
|
|
||||||
if not course_in_db:
|
if not course_in_db:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="Unavailable database",
|
||||||
|
)
|
||||||
|
|
||||||
return course.dict()
|
return course.dict()
|
||||||
|
|
||||||
|
|
||||||
async def update_course_thumbnail(request: Request, course_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
|
async def update_course_thumbnail(
|
||||||
|
request: Request,
|
||||||
|
course_id: str,
|
||||||
|
current_user: PublicUser,
|
||||||
|
thumbnail_file: UploadFile | None = None,
|
||||||
|
):
|
||||||
# verify course rights
|
# verify course rights
|
||||||
await verify_rights(request, course_id, current_user, "update")
|
await verify_rights(request, course_id, current_user, "update")
|
||||||
|
|
||||||
|
|
@ -178,26 +222,34 @@ async def update_course_thumbnail(request: Request, course_id: str, current_user
|
||||||
if course:
|
if course:
|
||||||
creationDate = course["creationDate"]
|
creationDate = course["creationDate"]
|
||||||
authors = course["authors"]
|
authors = course["authors"]
|
||||||
if thumbnail_file:
|
if thumbnail_file and thumbnail_file.filename:
|
||||||
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||||
course = Course(**course).copy(update={"thumbnail": name_in_disk})
|
course = Course(**course).copy(update={"thumbnail": name_in_disk})
|
||||||
await upload_thumbnail(thumbnail_file, name_in_disk)
|
await upload_thumbnail(thumbnail_file, name_in_disk)
|
||||||
|
|
||||||
updated_course = CourseInDB(course_id=course_id, creationDate=creationDate,
|
updated_course = CourseInDB(
|
||||||
authors=authors, updateDate=str(datetime.now()), **course.dict())
|
course_id=course_id,
|
||||||
|
creationDate=creationDate,
|
||||||
|
authors=authors,
|
||||||
|
updateDate=str(datetime.now()),
|
||||||
|
**course.dict(),
|
||||||
|
)
|
||||||
|
|
||||||
await courses.update_one({"course_id": course_id}, {
|
await courses.update_one(
|
||||||
"$set": updated_course.dict()})
|
{"course_id": course_id}, {"$set": updated_course.dict()}
|
||||||
|
)
|
||||||
|
|
||||||
return CourseInDB(**updated_course.dict())
|
return CourseInDB(**updated_course.dict())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def update_course(request: Request, course_object: Course, course_id: str, current_user: PublicUser):
|
async def update_course(
|
||||||
|
request: Request, course_object: Course, course_id: str, current_user: PublicUser
|
||||||
|
):
|
||||||
# verify course rights
|
# verify course rights
|
||||||
await verify_rights(request, course_id, current_user, "update")
|
await verify_rights(request, course_id, current_user, "update")
|
||||||
|
|
||||||
|
|
@ -213,20 +265,26 @@ async def update_course(request: Request, course_object: Course, course_id: str,
|
||||||
datetime_object = datetime.now()
|
datetime_object = datetime.now()
|
||||||
|
|
||||||
updated_course = CourseInDB(
|
updated_course = CourseInDB(
|
||||||
course_id=course_id, creationDate=creationDate, authors=authors, updateDate=str(datetime_object), **course_object.dict())
|
course_id=course_id,
|
||||||
|
creationDate=creationDate,
|
||||||
|
authors=authors,
|
||||||
|
updateDate=str(datetime_object),
|
||||||
|
**course_object.dict(),
|
||||||
|
)
|
||||||
|
|
||||||
await courses.update_one({"course_id": course_id}, {
|
await courses.update_one(
|
||||||
"$set": updated_course.dict()})
|
{"course_id": course_id}, {"$set": updated_course.dict()}
|
||||||
|
)
|
||||||
|
|
||||||
return CourseInDB(**updated_course.dict())
|
return CourseInDB(**updated_course.dict())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_course(request: Request, course_id: str, current_user: PublicUser):
|
async def delete_course(request: Request, course_id: str, current_user: PublicUser):
|
||||||
|
|
||||||
# verify course rights
|
# verify course rights
|
||||||
await verify_rights(request, course_id, current_user, "delete")
|
await verify_rights(request, course_id, current_user, "delete")
|
||||||
|
|
||||||
|
|
@ -236,7 +294,8 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs
|
||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
isDeleted = await courses.delete_one({"course_id": course_id})
|
isDeleted = await courses.delete_one({"course_id": course_id})
|
||||||
|
|
||||||
|
|
@ -244,24 +303,38 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs
|
||||||
return {"detail": "Course deleted"}
|
return {"detail": "Course deleted"}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="Unavailable database",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
####################################################
|
####################################################
|
||||||
# Misc
|
# Misc
|
||||||
####################################################
|
####################################################
|
||||||
|
|
||||||
|
|
||||||
async def get_courses(request: Request, page: int = 1, limit: int = 10, org_id: str | None = None):
|
async def get_courses(
|
||||||
|
request: Request, page: int = 1, limit: int = 10, org_id: str | None = None
|
||||||
|
):
|
||||||
courses = request.app.db["courses"]
|
courses = request.app.db["courses"]
|
||||||
# TODO : Get only courses that user is admin/has roles of
|
# TODO : Get only courses that user is admin/has roles of
|
||||||
# get all courses from database
|
# get all courses from database
|
||||||
all_courses = courses.find({"org_id": org_id}).sort(
|
all_courses = (
|
||||||
"name", 1).skip(10 * (page - 1)).limit(limit)
|
courses.find({"org_id": org_id})
|
||||||
|
.sort("name", 1)
|
||||||
|
.skip(10 * (page - 1))
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
|
||||||
return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)]
|
return [
|
||||||
|
json.loads(json.dumps(course, default=str))
|
||||||
|
for course in await all_courses.to_list(length=100)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None):
|
async def get_courses_orgslug(
|
||||||
|
request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None
|
||||||
|
):
|
||||||
courses = request.app.db["courses"]
|
courses = request.app.db["courses"]
|
||||||
orgs = request.app.db["organizations"]
|
orgs = request.app.db["organizations"]
|
||||||
# TODO : Get only courses that user is admin/has roles of
|
# TODO : Get only courses that user is admin/has roles of
|
||||||
|
|
@ -271,37 +344,61 @@ async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10,
|
||||||
|
|
||||||
if not org:
|
if not org:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
# get all courses from database
|
# get all courses from database
|
||||||
all_courses = courses.find({"org_id": org['org_id']}).sort(
|
all_courses = (
|
||||||
"name", 1).skip(10 * (page - 1)).limit(limit)
|
courses.find({"org_id": org["org_id"]})
|
||||||
|
.sort("name", 1)
|
||||||
|
.skip(10 * (page - 1))
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
|
||||||
return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)]
|
return [
|
||||||
|
json.loads(json.dumps(course, default=str))
|
||||||
|
for course in await all_courses.to_list(length=100)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#### Security ####################################################
|
#### Security ####################################################
|
||||||
|
|
||||||
|
|
||||||
async def verify_rights(request: Request, course_id: str, current_user: PublicUser | AnonymousUser, action: str):
|
async def verify_rights(
|
||||||
|
request: Request,
|
||||||
|
course_id: str,
|
||||||
|
current_user: PublicUser | AnonymousUser,
|
||||||
|
action: str,
|
||||||
|
):
|
||||||
courses = request.app.db["courses"]
|
courses = request.app.db["courses"]
|
||||||
|
|
||||||
course = await courses.find_one({"course_id": course_id})
|
course = await courses.find_one({"course_id": course_id})
|
||||||
|
|
||||||
if current_user.user_id == "anonymous" and course["public"] is True and action == "read":
|
if (
|
||||||
|
current_user.user_id == "anonymous"
|
||||||
|
and course["public"] is True
|
||||||
|
and action == "read"
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Course/CourseChapter does not exist")
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
|
detail="Course/CourseChapter does not exist",
|
||||||
|
)
|
||||||
|
|
||||||
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, course_id, course["org_id"])
|
hasRoleRights = await verify_user_rights_with_roles(
|
||||||
|
request, action, current_user.user_id, course_id, course["org_id"]
|
||||||
|
)
|
||||||
isAuthor = current_user.user_id in course["authors"]
|
isAuthor = current_user.user_id in course["authors"]
|
||||||
|
|
||||||
if not hasRoleRights and not isAuthor:
|
if not hasRoleRights and not isAuthor:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Roles/Ownership : Insufficient rights to perform this action")
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Roles/Ownership : Insufficient rights to perform this action",
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
#### Security ####################################################
|
#### Security ####################################################
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from src.services.courses.chapters import CourseChapter, create_coursechapter
|
||||||
from src.services.courses.activities.activities import Activity, create_activity
|
from src.services.courses.activities.activities import Activity, create_activity
|
||||||
from src.services.users.users import PublicUser, UserInDB
|
from src.services.users.users import PublicUser, UserInDB
|
||||||
|
|
||||||
from src.services.orgs import Organization, create_org
|
from src.services.orgs.orgs import Organization, create_org
|
||||||
from src.services.roles.schemas.roles import Permission, Elements, RoleInDB
|
from src.services.roles.schemas.roles import Permission, Elements, RoleInDB
|
||||||
from src.services.courses.courses import CourseInDB
|
from src.services.courses.courses import CourseInDB
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
@ -133,6 +133,7 @@ async def create_initial_data(request: Request):
|
||||||
description=fake.unique.text(),
|
description=fake.unique.text(),
|
||||||
email=fake.unique.email(),
|
email=fake.unique.email(),
|
||||||
slug=slug,
|
slug=slug,
|
||||||
|
logo="",
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
organizations.append(org)
|
organizations.append(org)
|
||||||
|
|
|
||||||
0
src/services/orgs/__init__.py
Normal file
0
src/services/orgs/__init__.py
Normal file
22
src/services/orgs/logos.py
Normal file
22
src/services/orgs/logos.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import os
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
|
async def upload_org_logo(logo_file):
|
||||||
|
contents = logo_file.file.read()
|
||||||
|
name_in_disk = f"{uuid4()}.{logo_file.filename.split('.')[-1]}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not os.path.exists("content/uploads/logos"):
|
||||||
|
os.makedirs("content/uploads/logos")
|
||||||
|
|
||||||
|
with open(f"content/uploads/logos/{name_in_disk}", "wb") as f:
|
||||||
|
f.write(contents)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return {"message": "There was an error uploading the file"}
|
||||||
|
finally:
|
||||||
|
logo_file.file.close()
|
||||||
|
|
||||||
|
return name_in_disk
|
||||||
|
|
@ -1,40 +1,16 @@
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from click import Option
|
from src.services.orgs.logos import upload_org_logo
|
||||||
from pydantic import BaseModel
|
from src.services.orgs.schemas.orgs import (
|
||||||
|
Organization,
|
||||||
|
OrganizationInDB,
|
||||||
|
PublicOrganization,
|
||||||
|
)
|
||||||
from src.services.users.schemas.users import UserOrganization
|
from src.services.users.schemas.users import UserOrganization
|
||||||
from src.services.users.users import PublicUser
|
from src.services.users.users import PublicUser
|
||||||
from src.security.security import *
|
from src.security.security import *
|
||||||
from fastapi import HTTPException, status, Request
|
from fastapi import HTTPException, UploadFile, status, Request
|
||||||
|
|
||||||
#### Classes ####################################################
|
|
||||||
|
|
||||||
|
|
||||||
class Organization(BaseModel):
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
email: str
|
|
||||||
slug: str
|
|
||||||
default: Optional[bool]
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationInDB(Organization):
|
|
||||||
org_id: str
|
|
||||||
|
|
||||||
|
|
||||||
class PublicOrganization(Organization):
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
email: str
|
|
||||||
slug: str
|
|
||||||
org_id: str
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
return getattr(self, item)
|
|
||||||
|
|
||||||
|
|
||||||
#### Classes ####################################################
|
|
||||||
|
|
||||||
|
|
||||||
async def get_organization(request: Request, org_id: str):
|
async def get_organization(request: Request, org_id: str):
|
||||||
|
|
@ -44,7 +20,8 @@ async def get_organization(request: Request, org_id: str):
|
||||||
|
|
||||||
if not org:
|
if not org:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
org = PublicOrganization(**org)
|
org = PublicOrganization(**org)
|
||||||
return org
|
return org
|
||||||
|
|
@ -57,13 +34,16 @@ async def get_organization_by_slug(request: Request, org_slug: str):
|
||||||
|
|
||||||
if not org:
|
if not org:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
org = PublicOrganization(**org)
|
org = PublicOrganization(**org)
|
||||||
return org
|
return org
|
||||||
|
|
||||||
|
|
||||||
async def create_org(request: Request, org_object: Organization, current_user: PublicUser):
|
async def create_org(
|
||||||
|
request: Request, org_object: Organization, current_user: PublicUser
|
||||||
|
):
|
||||||
orgs = request.app.db["organizations"]
|
orgs = request.app.db["organizations"]
|
||||||
user = request.app.db["users"]
|
user = request.app.db["users"]
|
||||||
|
|
||||||
|
|
@ -72,7 +52,9 @@ async def create_org(request: Request, org_object: Organization, current_user: P
|
||||||
|
|
||||||
if isOrgAvailable:
|
if isOrgAvailable:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization slug already exists")
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
|
detail="Organization slug already exists",
|
||||||
|
)
|
||||||
|
|
||||||
# generate org_id with uuid4
|
# generate org_id with uuid4
|
||||||
org_id = str(f"org_{uuid4()}")
|
org_id = str(f"org_{uuid4()}")
|
||||||
|
|
@ -82,25 +64,33 @@ async def create_org(request: Request, org_object: Organization, current_user: P
|
||||||
org_in_db = await orgs.insert_one(org.dict())
|
org_in_db = await orgs.insert_one(org.dict())
|
||||||
|
|
||||||
user_organization: UserOrganization = UserOrganization(
|
user_organization: UserOrganization = UserOrganization(
|
||||||
org_id=org_id, org_role="owner")
|
org_id=org_id, org_role="owner"
|
||||||
|
)
|
||||||
|
|
||||||
# add org to user
|
# add org to user
|
||||||
await user.update_one({"user_id": current_user.user_id}, {
|
await user.update_one(
|
||||||
"$addToSet": {"orgs": user_organization.dict()}})
|
{"user_id": current_user.user_id},
|
||||||
|
{"$addToSet": {"orgs": user_organization.dict()}},
|
||||||
|
)
|
||||||
|
|
||||||
# add role admin to org
|
# add role admin to org
|
||||||
await user.update_one({"user_id": current_user.user_id}, {
|
await user.update_one(
|
||||||
"$addToSet": {"roles": {"org_id": org_id, "role_id": "role_admin"}}})
|
{"user_id": current_user.user_id},
|
||||||
|
{"$addToSet": {"roles": {"org_id": org_id, "role_id": "role_admin"}}},
|
||||||
|
)
|
||||||
|
|
||||||
if not org_in_db:
|
if not org_in_db:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="Unavailable database",
|
||||||
|
)
|
||||||
|
|
||||||
return org.dict()
|
return org.dict()
|
||||||
|
|
||||||
|
|
||||||
async def update_org(request: Request, org_object: Organization, org_id: str, current_user: PublicUser):
|
async def update_org(
|
||||||
|
request: Request, org_object: Organization, org_id: str, current_user: PublicUser
|
||||||
|
):
|
||||||
# verify org rights
|
# verify org rights
|
||||||
await verify_org_rights(request, org_id, current_user, "update")
|
await verify_org_rights(request, org_id, current_user, "update")
|
||||||
|
|
||||||
|
|
@ -108,21 +98,38 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu
|
||||||
|
|
||||||
org = await orgs.find_one({"org_id": org_id})
|
org = await orgs.find_one({"org_id": org_id})
|
||||||
|
|
||||||
if not org:
|
updated_org = OrganizationInDB(org_id=org_id, **org_object.dict())
|
||||||
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
|
||||||
|
|
||||||
updated_org = OrganizationInDB(
|
|
||||||
org_id=org_id, **org_object.dict())
|
|
||||||
|
|
||||||
|
# update org
|
||||||
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
|
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
|
||||||
|
|
||||||
return Organization(**updated_org.dict())
|
|
||||||
|
return updated_org.dict()
|
||||||
|
|
||||||
|
|
||||||
|
async def update_org_logo(
|
||||||
|
request: Request, logo_file: UploadFile, org_id: str, current_user: PublicUser
|
||||||
|
):
|
||||||
|
# verify org rights
|
||||||
|
await verify_org_rights(request, org_id, current_user, "update")
|
||||||
|
|
||||||
|
orgs = request.app.db["organizations"]
|
||||||
|
|
||||||
|
org = await orgs.find_one({"org_id": org_id})
|
||||||
|
|
||||||
|
|
||||||
|
name_in_disk = await upload_org_logo(logo_file)
|
||||||
|
|
||||||
|
# update org
|
||||||
|
org = await orgs.update_one({"org_id": org_id}, {"$set": {"logo": name_in_disk}})
|
||||||
|
|
||||||
|
return {"detail": "Logo updated"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_org(request: Request, org_id: str, current_user: PublicUser):
|
async def delete_org(request: Request, org_id: str, current_user: PublicUser):
|
||||||
|
|
||||||
await verify_org_rights(request, org_id, current_user, "delete")
|
await verify_org_rights(request, org_id, current_user, "delete")
|
||||||
|
|
||||||
orgs = request.app.db["organizations"]
|
orgs = request.app.db["organizations"]
|
||||||
|
|
@ -131,7 +138,8 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser):
|
||||||
|
|
||||||
if not org:
|
if not org:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
isDeleted = await orgs.delete_one({"org_id": org_id})
|
isDeleted = await orgs.delete_one({"org_id": org_id})
|
||||||
|
|
||||||
|
|
@ -143,18 +151,22 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser):
|
||||||
return {"detail": "Org deleted"}
|
return {"detail": "Org deleted"}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="Unavailable database",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: int = 10):
|
async def get_orgs_by_user(
|
||||||
|
request: Request, user_id: str, page: int = 1, limit: int = 10
|
||||||
|
):
|
||||||
orgs = request.app.db["organizations"]
|
orgs = request.app.db["organizations"]
|
||||||
user = request.app.db["users"]
|
user = request.app.db["users"]
|
||||||
|
|
||||||
if user_id == "anonymous":
|
if user_id == "anonymous":
|
||||||
|
|
||||||
# raise error
|
# raise error
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="User not logged in")
|
status_code=status.HTTP_409_CONFLICT, detail="User not logged in"
|
||||||
|
)
|
||||||
|
|
||||||
# get user orgs
|
# get user orgs
|
||||||
user_orgs = await user.find_one({"user_id": user_id})
|
user_orgs = await user.find_one({"user_id": user_id})
|
||||||
|
|
@ -162,37 +174,57 @@ async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit:
|
||||||
org_ids: list[UserOrganization] = []
|
org_ids: list[UserOrganization] = []
|
||||||
|
|
||||||
for org in user_orgs["orgs"]:
|
for org in user_orgs["orgs"]:
|
||||||
if org["org_role"] == "owner" or org["org_role"] == "editor" or org["org_role"] == "member":
|
if (
|
||||||
|
org["org_role"] == "owner"
|
||||||
|
or org["org_role"] == "editor"
|
||||||
|
or org["org_role"] == "member"
|
||||||
|
):
|
||||||
org_ids.append(org["org_id"])
|
org_ids.append(org["org_id"])
|
||||||
|
|
||||||
# find all orgs where org_id is in org_ids array
|
# find all orgs where org_id is in org_ids array
|
||||||
|
|
||||||
all_orgs = orgs.find({"org_id": {"$in": org_ids}}).sort(
|
all_orgs = (
|
||||||
"name", 1).skip(10 * (page - 1)).limit(100)
|
orgs.find({"org_id": {"$in": org_ids}})
|
||||||
|
.sort("name", 1)
|
||||||
|
.skip(10 * (page - 1))
|
||||||
|
.limit(100)
|
||||||
|
)
|
||||||
|
|
||||||
return [json.loads(json.dumps(org, default=str)) for org in await all_orgs.to_list(length=100)]
|
return [
|
||||||
|
json.loads(json.dumps(org, default=str))
|
||||||
|
for org in await all_orgs.to_list(length=100)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#### Security ####################################################
|
#### Security ####################################################
|
||||||
|
|
||||||
async def verify_org_rights(request: Request, org_id: str, current_user: PublicUser, action: str,):
|
|
||||||
|
async def verify_org_rights(
|
||||||
|
request: Request,
|
||||||
|
org_id: str,
|
||||||
|
current_user: PublicUser,
|
||||||
|
action: str,
|
||||||
|
):
|
||||||
orgs = request.app.db["organizations"]
|
orgs = request.app.db["organizations"]
|
||||||
|
|
||||||
org = await orgs.find_one({"org_id": org_id})
|
org = await orgs.find_one({"org_id": org_id})
|
||||||
|
|
||||||
if not org:
|
if not org:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
|
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
# check if is owner of org
|
hasRoleRights = await verify_user_rights_with_roles(
|
||||||
# todo check if is admin of org
|
request, action, current_user.user_id, org_id, org_id
|
||||||
|
)
|
||||||
|
|
||||||
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, org_id, org_id)
|
if not hasRoleRights:
|
||||||
|
raise HTTPException(
|
||||||
# if not hasRoleRights and not isOwner:
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
# raise HTTPException(
|
detail="You do not have rights to this organization",
|
||||||
# status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this organization")
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
#### Security ####################################################
|
#### Security ####################################################
|
||||||
0
src/services/orgs/schemas/__init__.py
Normal file
0
src/services/orgs/schemas/__init__.py
Normal file
29
src/services/orgs/schemas/orgs.py
Normal file
29
src/services/orgs/schemas/orgs.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from src.security.security import *
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Organization(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
email: str
|
||||||
|
slug: str
|
||||||
|
logo: Optional[str]
|
||||||
|
default: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationInDB(Organization):
|
||||||
|
org_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class PublicOrganization(Organization):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
email: str
|
||||||
|
slug: str
|
||||||
|
org_id: str
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return getattr(self, item)
|
||||||
|
|
@ -4,7 +4,7 @@ from uuid import uuid4
|
||||||
from fastapi import HTTPException, Request, status
|
from fastapi import HTTPException, Request, status
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from src.services.courses.chapters import get_coursechapters_meta
|
from src.services.courses.chapters import get_coursechapters_meta
|
||||||
from src.services.orgs import PublicOrganization
|
from src.services.orgs.orgs import PublicOrganization
|
||||||
|
|
||||||
from src.services.users.users import PublicUser
|
from src.services.users.users import PublicUser
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue