mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #318 from learnhouse/feat/seo-improvements-2
SEO Improvements : Part 2
This commit is contained in:
commit
328c602176
25 changed files with 789 additions and 199 deletions
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""Add thumbnail image to orgs
|
||||||
|
|
||||||
|
Revision ID: 040ccb1d456e
|
||||||
|
Revises: 83b6d9d6f57a
|
||||||
|
Create Date: 2024-09-27 10:23:50.508031
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa # noqa: F401
|
||||||
|
import sqlmodel # noqa: F401
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '040ccb1d456e'
|
||||||
|
down_revision: Union[str, None] = '83b6d9d6f57a'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('organization', sa.Column('thumbnail_image', sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('organization', 'thumbnail_image')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -12,6 +12,7 @@ class OrganizationBase(SQLModel):
|
||||||
slug: str
|
slug: str
|
||||||
email: str
|
email: str
|
||||||
logo_image: Optional[str]
|
logo_image: Optional[str]
|
||||||
|
thumbnail_image: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class Organization(OrganizationBase, table=True):
|
class Organization(OrganizationBase, table=True):
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ from src.services.orgs.orgs import (
|
||||||
update_org,
|
update_org,
|
||||||
update_org_logo,
|
update_org_logo,
|
||||||
update_org_signup_mechanism,
|
update_org_signup_mechanism,
|
||||||
|
update_org_thumbnail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -303,7 +304,7 @@ async def api_update_org_logo(
|
||||||
db_session: Session = Depends(get_db_session),
|
db_session: Session = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get single Org by Slug
|
Update org logo
|
||||||
"""
|
"""
|
||||||
return await update_org_logo(
|
return await update_org_logo(
|
||||||
request=request,
|
request=request,
|
||||||
|
|
@ -314,6 +315,26 @@ async def api_update_org_logo(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{org_id}/thumbnail")
|
||||||
|
async def api_update_org_thumbnail(
|
||||||
|
request: Request,
|
||||||
|
org_id: str,
|
||||||
|
thumbnail_file: UploadFile,
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Update org thumbnail
|
||||||
|
"""
|
||||||
|
return await update_org_thumbnail(
|
||||||
|
request=request,
|
||||||
|
thumbnail_file=thumbnail_file,
|
||||||
|
org_id=org_id,
|
||||||
|
current_user=current_user,
|
||||||
|
db_session=db_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user/page/{page}/limit/{limit}")
|
@router.get("/user/page/{page}/limit/{limit}")
|
||||||
async def api_user_orgs(
|
async def api_user_orgs(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,10 @@ from src.db.organizations import (
|
||||||
OrganizationRead,
|
OrganizationRead,
|
||||||
OrganizationUpdate,
|
OrganizationUpdate,
|
||||||
)
|
)
|
||||||
from src.services.orgs.logos import upload_org_logo
|
|
||||||
from fastapi import HTTPException, UploadFile, status, Request
|
from fastapi import HTTPException, UploadFile, status, Request
|
||||||
|
|
||||||
|
from src.services.orgs.uploads import upload_org_logo, upload_org_thumbnail
|
||||||
|
|
||||||
|
|
||||||
async def get_organization(
|
async def get_organization(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -421,6 +422,42 @@ async def update_org_logo(
|
||||||
|
|
||||||
return {"detail": "Logo updated"}
|
return {"detail": "Logo updated"}
|
||||||
|
|
||||||
|
async def update_org_thumbnail(
|
||||||
|
request: Request,
|
||||||
|
thumbnail_file: UploadFile,
|
||||||
|
org_id: str,
|
||||||
|
current_user: PublicUser | AnonymousUser,
|
||||||
|
db_session: Session,
|
||||||
|
):
|
||||||
|
statement = select(Organization).where(Organization.id == org_id)
|
||||||
|
result = db_session.exec(statement)
|
||||||
|
|
||||||
|
org = result.first()
|
||||||
|
|
||||||
|
if not org:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Organization not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# RBAC check
|
||||||
|
await rbac_check(request, org.org_uuid, current_user, "update", db_session)
|
||||||
|
|
||||||
|
# Upload logo
|
||||||
|
name_in_disk = await upload_org_thumbnail(thumbnail_file, org.org_uuid)
|
||||||
|
|
||||||
|
# Update org
|
||||||
|
org.thumbnail_image = name_in_disk
|
||||||
|
|
||||||
|
# Complete the org object
|
||||||
|
org.update_date = str(datetime.now())
|
||||||
|
|
||||||
|
db_session.add(org)
|
||||||
|
db_session.commit()
|
||||||
|
db_session.refresh(org)
|
||||||
|
|
||||||
|
return {"detail": "Thumbnail updated"}
|
||||||
|
|
||||||
|
|
||||||
async def delete_org(
|
async def delete_org(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,18 @@ async def upload_org_logo(logo_file, org_uuid):
|
||||||
)
|
)
|
||||||
|
|
||||||
return name_in_disk
|
return name_in_disk
|
||||||
|
|
||||||
|
|
||||||
|
async def upload_org_thumbnail(thumbnail_file, org_uuid):
|
||||||
|
contents = thumbnail_file.file.read()
|
||||||
|
name_in_disk = f"{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||||
|
|
||||||
|
await upload_content(
|
||||||
|
"thumbnails",
|
||||||
|
"orgs",
|
||||||
|
org_uuid,
|
||||||
|
contents,
|
||||||
|
name_in_disk,
|
||||||
|
)
|
||||||
|
|
||||||
|
return name_in_disk
|
||||||
72
apps/web/app/api/sitemap/route.ts
Normal file
72
apps/web/app/api/sitemap/route.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { getUriWithOrg } from '@services/config/config';
|
||||||
|
import { getOrgCourses } from '@services/courses/courses';
|
||||||
|
import { getOrganizationContextInfo } from '@services/organizations/orgs';
|
||||||
|
import { getOrgCollections } from '@services/courses/collections';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const orgSlug = request.headers.get('X-Sitemap-Orgslug');
|
||||||
|
|
||||||
|
if (!orgSlug) {
|
||||||
|
return NextResponse.json({ error: 'Missing X-Sitemap-Orgslug header' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgInfo = await getOrganizationContextInfo(orgSlug, null);
|
||||||
|
const courses = await getOrgCourses(orgSlug, null);
|
||||||
|
const collections = await getOrgCollections(orgInfo.id);
|
||||||
|
|
||||||
|
const host = request.headers.get('host');
|
||||||
|
if (!host) {
|
||||||
|
return NextResponse.json({ error: 'Missing host header' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = getUriWithOrg(orgSlug, '/');
|
||||||
|
|
||||||
|
const sitemapUrls: SitemapUrl[] = [
|
||||||
|
{ loc: baseUrl, priority: 1.0, changefreq: 'daily' },
|
||||||
|
{ loc: `${baseUrl}collections`, priority: 0.9, changefreq: 'weekly' },
|
||||||
|
{ loc: `${baseUrl}courses`, priority: 0.9, changefreq: 'weekly' },
|
||||||
|
// Courses
|
||||||
|
...courses.map((course: { course_uuid: string }) => ({
|
||||||
|
loc: `${baseUrl}course/${course.course_uuid.replace('course_', '')}`,
|
||||||
|
priority: 0.7,
|
||||||
|
changefreq: 'weekly'
|
||||||
|
})),
|
||||||
|
// Collections
|
||||||
|
...collections.map((collection: { collection_uuid: string }) => ({
|
||||||
|
loc: `${baseUrl}collections/${collection.collection_uuid.replace('collection_', '')}`,
|
||||||
|
priority: 0.6,
|
||||||
|
changefreq: 'weekly'
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
const sitemap = generateSitemap(baseUrl, sitemapUrls);
|
||||||
|
|
||||||
|
return new NextResponse(sitemap, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/xml',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SitemapUrl {
|
||||||
|
loc: string;
|
||||||
|
priority: number;
|
||||||
|
changefreq: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSitemap(baseUrl: string, urls: SitemapUrl[]): string {
|
||||||
|
const urlEntries = urls.map(({ loc, priority, changefreq }) => `
|
||||||
|
<url>
|
||||||
|
<loc>${loc}</loc>
|
||||||
|
<priority>${priority.toFixed(1)}</priority>
|
||||||
|
<changefreq>${changefreq}</changefreq>
|
||||||
|
</url>`).join('');
|
||||||
|
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
${urlEntries}
|
||||||
|
</urlset>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
|
||||||
import { nextAuthOptions } from 'app/auth/options'
|
import { nextAuthOptions } from 'app/auth/options'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { getOrgCollections } from '@services/courses/collections'
|
import { getOrgCollections } from '@services/courses/collections'
|
||||||
|
import { getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||||
|
|
||||||
type MetadataProps = {
|
type MetadataProps = {
|
||||||
params: { orgslug: string; courseid: string }
|
params: { orgslug: string; courseid: string }
|
||||||
|
|
@ -44,6 +45,14 @@ export async function generateMetadata({
|
||||||
title: `Collections — ${org.name}`,
|
title: `Collections — ${org.name}`,
|
||||||
description: `Collections of courses from ${org.name}`,
|
description: `Collections of courses from ${org.name}`,
|
||||||
type: 'website',
|
type: 'website',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image),
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
alt: org.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { getOrganizationContextInfo } from '@services/organizations/orgs'
|
||||||
import { nextAuthOptions } from 'app/auth/options'
|
import { nextAuthOptions } from 'app/auth/options'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { getOrgCourses } from '@services/courses/courses'
|
import { getOrgCourses } from '@services/courses/courses'
|
||||||
|
import { getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||||
|
|
||||||
type MetadataProps = {
|
type MetadataProps = {
|
||||||
params: { orgslug: string }
|
params: { orgslug: string }
|
||||||
|
|
@ -39,6 +40,14 @@ export async function generateMetadata({
|
||||||
title: 'Courses — ' + org.name,
|
title: 'Courses — ' + org.name,
|
||||||
description: org.description,
|
description: org.description,
|
||||||
type: 'website',
|
type: 'website',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image),
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
alt: org.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder'
|
||||||
import { getOrgCollections } from '@services/courses/collections'
|
import { getOrgCollections } from '@services/courses/collections'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { nextAuthOptions } from 'app/auth/options'
|
import { nextAuthOptions } from 'app/auth/options'
|
||||||
|
import { getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||||
|
|
||||||
type MetadataProps = {
|
type MetadataProps = {
|
||||||
params: { orgslug: string }
|
params: { orgslug: string }
|
||||||
|
|
@ -48,6 +49,14 @@ export async function generateMetadata({
|
||||||
title: `Home — ${org.name}`,
|
title: `Home — ${org.name}`,
|
||||||
description: org.description,
|
description: org.description,
|
||||||
type: 'website',
|
type: 'website',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image),
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
alt: org.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
|
||||||
import { getUriWithOrg } from '@services/config/config'
|
import { getUriWithOrg } from '@services/config/config'
|
||||||
import { Info } from 'lucide-react'
|
import { Info } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import OrgEditGeneral from '@components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral'
|
import OrgEditGeneral from '@components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral'
|
||||||
|
|
||||||
|
|
@ -13,14 +13,31 @@ export type OrgParams = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrgPage({ params }: { params: OrgParams }) {
|
function OrgPage({ params }: { params: OrgParams }) {
|
||||||
|
const [H1Label, setH1Label] = React.useState('')
|
||||||
|
const [H2Label, setH2Label] = React.useState('')
|
||||||
|
|
||||||
|
function handleLabels() {
|
||||||
|
if (params.subpage == 'general') {
|
||||||
|
setH1Label('General')
|
||||||
|
setH2Label('Manage your organization settings')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleLabels()
|
||||||
|
}, [params.subpage, params])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full bg-[#f8f8f8]">
|
<div className="h-full w-full bg-[#f8f8f8]">
|
||||||
<div className="pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]">
|
<div className="pl-10 pr-10 tracking-tight bg-[#fcfbfc] shadow-[0px_4px_16px_rgba(0,0,0,0.02)]">
|
||||||
<BreadCrumbs type="org"></BreadCrumbs>
|
<BreadCrumbs type="org"></BreadCrumbs>
|
||||||
<div className="my-2 tracking-tighter">
|
<div className="my-2 py-3">
|
||||||
<div className="w-100 flex justify-between">
|
<div className="w-100 flex flex-col space-y-1">
|
||||||
<div className="pt-3 flex font-bold text-4xl">
|
<div className="pt-3 flex font-bold text-4xl tracking-tighter">
|
||||||
Organization Settings
|
{H1Label}
|
||||||
|
</div>
|
||||||
|
<div className="flex font-medium text-gray-400 text-md">
|
||||||
|
{H2Label}{' '}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
'use client'
|
|
||||||
import { motion } from 'framer-motion'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import learnhouseBigIcon from 'public/learnhouse_bigicon.png'
|
|
||||||
import Image from 'next/legacy/image'
|
|
||||||
import Link from 'next/link'
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<HomePage>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{
|
|
||||||
type: 'spring',
|
|
||||||
stiffness: 260,
|
|
||||||
damping: 70,
|
|
||||||
delay: 0.2,
|
|
||||||
}}
|
|
||||||
exit={{ opacity: 1 }}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
alt="Learnhouse Icon"
|
|
||||||
height={260}
|
|
||||||
width={260}
|
|
||||||
quality={100}
|
|
||||||
src={learnhouseBigIcon}
|
|
||||||
></Image>
|
|
||||||
</motion.div>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{
|
|
||||||
type: 'spring',
|
|
||||||
stiffness: 260,
|
|
||||||
damping: 70,
|
|
||||||
delay: 0.8,
|
|
||||||
}}
|
|
||||||
exit={{ opacity: 1 }}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Link href={'/organizations'}>
|
|
||||||
<OrgsButton>See Organizations</OrgsButton>
|
|
||||||
</Link>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<Link href={'/login'}>
|
|
||||||
<OrgsButton>Login</OrgsButton>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</HomePage>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const OrgsButton = styled.button`
|
|
||||||
background: #151515;
|
|
||||||
border: 1px solid #e5e5e50a;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
color: white;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
margin: 0 10px;
|
|
||||||
margin: auto;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: 'DM Sans';
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 12px;
|
|
||||||
-webkit-transition: all 0.2s ease-in-out;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
&:hover {
|
|
||||||
background: #191919;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const HomePage = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: linear-gradient(131.61deg, #202020 7.15%, #000000 90.96%);
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
img {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
20
apps/web/components.json
Normal file
20
apps/web/components.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "styles/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,16 @@ import { Field, Form, Formik } from 'formik'
|
||||||
import {
|
import {
|
||||||
updateOrganization,
|
updateOrganization,
|
||||||
uploadOrganizationLogo,
|
uploadOrganizationLogo,
|
||||||
|
uploadOrganizationThumbnail,
|
||||||
} from '@services/settings/org'
|
} from '@services/settings/org'
|
||||||
import { UploadCloud } from 'lucide-react'
|
import { UploadCloud, Info, Check, FileWarning } from 'lucide-react'
|
||||||
import { revalidateTags } from '@services/utils/ts/requests'
|
import { revalidateTags } from '@services/utils/ts/requests'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useOrg } from '@components/Contexts/OrgContext'
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
|
import { getOrgLogoMediaDirectory, getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
|
import { Toaster, toast } from 'react-hot-toast';
|
||||||
|
|
||||||
interface OrganizationValues {
|
interface OrganizationValues {
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -17,32 +21,54 @@ interface OrganizationValues {
|
||||||
slug: string
|
slug: string
|
||||||
logo: string
|
logo: string
|
||||||
email: string
|
email: string
|
||||||
|
thumbnail: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrgEditGeneral(props: any) {
|
function OrgEditGeneral() {
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
const router = useRouter()
|
||||||
const router = useRouter();
|
const session = useLHSession() as any
|
||||||
const session = useLHSession() as any;
|
const access_token = session?.data?.tokens?.access_token
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
|
||||||
const org = useOrg() as any
|
const org = useOrg() as any
|
||||||
// ...
|
const [selectedTab, setSelectedTab] = useState<'logo' | 'thumbnail'>('logo');
|
||||||
|
const [localLogo, setLocalLogo] = useState<string | null>(null);
|
||||||
|
const [localThumbnail, setLocalThumbnail] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.files && event.target.files.length > 0) {
|
if (event.target.files && event.target.files.length > 0) {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
setSelectedFile(file)
|
setLocalLogo(URL.createObjectURL(file))
|
||||||
|
const loadingToast = toast.loading('Uploading logo...');
|
||||||
|
try {
|
||||||
|
await uploadOrganizationLogo(org.id, file, access_token)
|
||||||
|
await new Promise((r) => setTimeout(r, 1500))
|
||||||
|
toast.success('Logo Updated', { id: loadingToast });
|
||||||
|
router.refresh()
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Failed to upload logo', { id: loadingToast });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadLogo = async () => {
|
const handleThumbnailChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (selectedFile) {
|
if (event.target.files && event.target.files.length > 0) {
|
||||||
let org_id = org.id
|
const file = event.target.files[0];
|
||||||
await uploadOrganizationLogo(org_id, selectedFile, access_token)
|
setLocalThumbnail(URL.createObjectURL(file));
|
||||||
setSelectedFile(null) // Reset the selected file
|
const loadingToast = toast.loading('Uploading thumbnail...');
|
||||||
await revalidateTags(['organizations'], org.slug)
|
try {
|
||||||
router.refresh()
|
await uploadOrganizationThumbnail(org.id, file, access_token);
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
|
toast.success('Thumbnail Updated', { id: loadingToast });
|
||||||
|
router.refresh()
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Failed to upload thumbnail', { id: loadingToast });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleImageButtonClick = (inputId: string) => (event: React.MouseEvent) => {
|
||||||
|
event.preventDefault(); // Prevent form submission
|
||||||
|
document.getElementById(inputId)?.click();
|
||||||
|
};
|
||||||
|
|
||||||
let orgValues: OrganizationValues = {
|
let orgValues: OrganizationValues = {
|
||||||
name: org?.name,
|
name: org?.name,
|
||||||
|
|
@ -50,21 +76,25 @@ function OrgEditGeneral(props: any) {
|
||||||
slug: org?.slug,
|
slug: org?.slug,
|
||||||
logo: org?.logo,
|
logo: org?.logo,
|
||||||
email: org?.email,
|
email: org?.email,
|
||||||
|
thumbnail: org?.thumbnail,
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateOrg = async (values: OrganizationValues) => {
|
const updateOrg = async (values: OrganizationValues) => {
|
||||||
let org_id = org.id
|
const loadingToast = toast.loading('Updating organization...');
|
||||||
await updateOrganization(org_id, values, access_token)
|
try {
|
||||||
|
await updateOrganization(org.id, values, access_token)
|
||||||
// Mutate the org
|
await revalidateTags(['organizations'], org.slug)
|
||||||
await revalidateTags(['organizations'], org.slug)
|
toast.success('Organization Updated', { id: loadingToast });
|
||||||
router.refresh()
|
} catch (err) {
|
||||||
|
toast.error('Failed to update organization', { id: loadingToast });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { }, [org])
|
useEffect(() => {}, [org])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
|
<div className="ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-6 py-5">
|
||||||
|
<Toaster />
|
||||||
<Formik
|
<Formik
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
initialValues={orgValues}
|
initialValues={orgValues}
|
||||||
|
|
@ -77,71 +107,127 @@ function OrgEditGeneral(props: any) {
|
||||||
>
|
>
|
||||||
{({ isSubmitting }) => (
|
{({ isSubmitting }) => (
|
||||||
<Form>
|
<Form>
|
||||||
<label className="block mb-2 font-bold" htmlFor="name">
|
<div className="flex space-x-8">
|
||||||
Name
|
<div className="max-w-md flex-grow">
|
||||||
</label>
|
<label className="block mb-2 font-bold" htmlFor="name">
|
||||||
<Field
|
Name
|
||||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
</label>
|
||||||
type="text"
|
<Field
|
||||||
name="name"
|
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
/>
|
type="text"
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
|
||||||
<label className="block mb-2 font-bold" htmlFor="description">
|
<label className="block mb-2 font-bold" htmlFor="description">
|
||||||
Description
|
Description
|
||||||
</label>
|
</label>
|
||||||
<Field
|
<Field
|
||||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
type="text"
|
type="text"
|
||||||
name="description"
|
name="description"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label className="block mb-2 font-bold" htmlFor="slug">
|
<label className="block mb-2 font-bold" htmlFor="slug">
|
||||||
Logo
|
Slug
|
||||||
</label>
|
</label>
|
||||||
|
<Field
|
||||||
|
className="w-full px-4 py-2 mb-4 border rounded-lg bg-gray-200 cursor-not-allowed"
|
||||||
|
disabled
|
||||||
|
type="text"
|
||||||
|
name="slug"
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-center w-full ">
|
<label className="block mb-2 font-bold" htmlFor="email">
|
||||||
<input
|
Email
|
||||||
className="w-full px-4 py-2 mr-1 border rounded-lg bg-gray-200 cursor-not-allowed"
|
</label>
|
||||||
type="file"
|
<Field
|
||||||
name="logo"
|
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
onChange={handleFileChange}
|
type="email"
|
||||||
/>
|
name="email"
|
||||||
<button
|
/>
|
||||||
type="button"
|
|
||||||
onClick={uploadLogo}
|
<button
|
||||||
disabled={isSubmitting || selectedFile === null}
|
type="submit"
|
||||||
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"
|
disabled={isSubmitting}
|
||||||
>
|
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-black"
|
||||||
<UploadCloud size={24} />
|
>
|
||||||
</button>
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col grow space-y-3">
|
||||||
|
<Tabs defaultValue="logo" className="w-full ">
|
||||||
|
<TabsList className="grid w-full grid-cols-2 mb-20">
|
||||||
|
<TabsTrigger value="logo">Logo</TabsTrigger>
|
||||||
|
<TabsTrigger value="thumbnail">Thumbnail</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="logo">
|
||||||
|
<div className="flex flex-col space-y-3">
|
||||||
|
<div className="w-auto bg-gray-50 rounded-xl outline outline-1 outline-gray-200 h-[200px] shadow mx-20">
|
||||||
|
<div className="flex flex-col justify-center items-center mt-10">
|
||||||
|
<div
|
||||||
|
className="w-[200px] h-[100px] bg-contain bg-no-repeat bg-center rounded-lg nice-shadow bg-white"
|
||||||
|
style={{ backgroundImage: `url(${localLogo || getOrgLogoMediaDirectory(org?.org_uuid, org?.logo_image)})` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="font-bold antialiased items-center text-gray text-sm rounded-md px-4 py-2 mt-4 flex"
|
||||||
|
onClick={handleImageButtonClick('fileInput')}
|
||||||
|
>
|
||||||
|
<UploadCloud size={16} className="mr-2" />
|
||||||
|
<span>Change Logo</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex text-xs space-x-2 items-center text-gray-500 justify-center">
|
||||||
|
<Info size={13} />
|
||||||
|
<p>Accepts PNG , JPG</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="thumbnail">
|
||||||
|
<div className="flex flex-col space-y-3">
|
||||||
|
<div className="w-auto bg-gray-50 rounded-xl outline outline-1 outline-gray-200 h-[200px] shadow mx-20">
|
||||||
|
<div className="flex flex-col justify-center items-center mt-10">
|
||||||
|
<div
|
||||||
|
className="w-[200px] h-[100px] bg-contain bg-no-repeat bg-center rounded-lg nice-shadow bg-white"
|
||||||
|
style={{ backgroundImage: `url(${localThumbnail || getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image)})` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="thumbnailInput"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleThumbnailChange}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="font-bold antialiased items-center text-gray text-sm rounded-md px-4 py-2 mt-4 flex"
|
||||||
|
onClick={handleImageButtonClick('thumbnailInput')}
|
||||||
|
>
|
||||||
|
<UploadCloud size={16} className="mr-2" />
|
||||||
|
<span>Change Thumbnail</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex text-xs space-x-2 items-center text-gray-500 justify-center">
|
||||||
|
<Info size={13} />
|
||||||
|
<p>Accepts PNG, JPG</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="block mb-2 font-bold" htmlFor="slug">
|
|
||||||
Slug
|
|
||||||
</label>
|
|
||||||
<Field
|
|
||||||
className="w-full px-4 py-2 mb-4 border rounded-lg bg-gray-200 cursor-not-allowed"
|
|
||||||
disabled
|
|
||||||
type="text"
|
|
||||||
name="slug"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label className="block mb-2 font-bold" htmlFor="email">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<Field
|
|
||||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-black"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
|
||||||
55
apps/web/components/ui/tabs.tsx
Normal file
55
apps/web/components/ui/tabs.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
6
apps/web/lib/utils.ts
Normal file
6
apps/web/lib/utils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ export const config = {
|
||||||
* 5. all root files inside /public (e.g. /favicon.ico)
|
* 5. all root files inside /public (e.g. /favicon.ico)
|
||||||
*/
|
*/
|
||||||
'/((?!api|_next|fonts|umami|examples|[\\w-]+\\.\\w+).*)',
|
'/((?!api|_next|fonts|umami|examples|[\\w-]+\\.\\w+).*)',
|
||||||
|
'/sitemap.xml',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +100,29 @@ export default async function middleware(req: NextRequest) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pathname.startsWith('/sitemap.xml')) {
|
||||||
|
let orgslug: string;
|
||||||
|
|
||||||
|
if (hosting_mode === 'multi') {
|
||||||
|
orgslug = fullhost
|
||||||
|
? fullhost.replace(`.${LEARNHOUSE_DOMAIN}`, '')
|
||||||
|
: (default_org as string);
|
||||||
|
} else {
|
||||||
|
// Single hosting mode
|
||||||
|
orgslug = default_org as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sitemapUrl = new URL(`/api/sitemap`, req.url);
|
||||||
|
|
||||||
|
// Create a response object
|
||||||
|
const response = NextResponse.rewrite(sitemapUrl);
|
||||||
|
|
||||||
|
// Set the orgslug in a header
|
||||||
|
response.headers.set('X-Sitemap-Orgslug', orgslug);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
// Multi Organization Mode
|
// Multi Organization Mode
|
||||||
if (hosting_mode === 'multi') {
|
if (hosting_mode === 'multi') {
|
||||||
// Get the organization slug from the URL
|
// Get the organization slug from the URL
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"@radix-ui/react-form": "^0.0.3",
|
"@radix-ui/react-form": "^0.0.3",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-switch": "^1.1.0",
|
"@radix-ui/react-switch": "^1.1.0",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@sentry/nextjs": "^8.27.0",
|
"@sentry/nextjs": "^8.27.0",
|
||||||
"@stitches/react": "^1.2.8",
|
"@stitches/react": "^1.2.8",
|
||||||
|
|
@ -32,6 +33,8 @@
|
||||||
"@tiptap/starter-kit": "^2.6.6",
|
"@tiptap/starter-kit": "^2.6.6",
|
||||||
"@types/randomcolor": "^0.5.9",
|
"@types/randomcolor": "^0.5.9",
|
||||||
"avvvatars-react": "^0.4.2",
|
"avvvatars-react": "^0.4.2",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
|
|
@ -59,6 +62,7 @@
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"y-indexeddb": "^9.0.12",
|
"y-indexeddb": "^9.0.12",
|
||||||
"y-prosemirror": "^1.2.12",
|
"y-prosemirror": "^1.2.12",
|
||||||
|
|
|
||||||
145
apps/web/pnpm-lock.yaml
generated
145
apps/web/pnpm-lock.yaml
generated
|
|
@ -29,6 +29,9 @@ importers:
|
||||||
'@radix-ui/react-switch':
|
'@radix-ui/react-switch':
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-tabs':
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@radix-ui/react-tooltip':
|
'@radix-ui/react-tooltip':
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.1.2(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
@ -71,6 +74,12 @@ importers:
|
||||||
avvvatars-react:
|
avvvatars-react:
|
||||||
specifier: ^0.4.2
|
specifier: ^0.4.2
|
||||||
version: 0.4.2(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.4.2(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
class-variance-authority:
|
||||||
|
specifier: ^0.7.0
|
||||||
|
version: 0.7.0
|
||||||
|
clsx:
|
||||||
|
specifier: ^2.1.1
|
||||||
|
version: 2.1.1
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
|
@ -152,6 +161,9 @@ importers:
|
||||||
tailwind-scrollbar:
|
tailwind-scrollbar:
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0(tailwindcss@3.4.10)
|
version: 3.1.0(tailwindcss@3.4.10)
|
||||||
|
tailwindcss-animate:
|
||||||
|
specifier: ^1.0.7
|
||||||
|
version: 1.0.7(tailwindcss@3.4.10)
|
||||||
uuid:
|
uuid:
|
||||||
specifier: ^9.0.1
|
specifier: ^9.0.1
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
|
|
@ -799,6 +811,19 @@ packages:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-collection@1.1.0':
|
||||||
|
resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-compose-refs@1.0.1':
|
'@radix-ui/react-compose-refs@1.0.1':
|
||||||
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -848,6 +873,15 @@ packages:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-direction@1.1.0':
|
||||||
|
resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.1.0':
|
'@radix-ui/react-dismissable-layer@1.1.0':
|
||||||
resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==}
|
resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -997,6 +1031,19 @@ packages:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.0':
|
||||||
|
resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.0.2':
|
'@radix-ui/react-slot@1.0.2':
|
||||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -1028,6 +1075,19 @@ packages:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-tabs@1.1.0':
|
||||||
|
resolution: {integrity: sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.1.2':
|
'@radix-ui/react-tooltip@1.1.2':
|
||||||
resolution: {integrity: sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==}
|
resolution: {integrity: sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -1817,9 +1877,20 @@ packages:
|
||||||
cjs-module-lexer@1.4.0:
|
cjs-module-lexer@1.4.0:
|
||||||
resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==}
|
resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==}
|
||||||
|
|
||||||
|
class-variance-authority@0.7.0:
|
||||||
|
resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==}
|
||||||
|
|
||||||
client-only@0.0.1:
|
client-only@0.0.1:
|
||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
|
||||||
|
clsx@2.0.0:
|
||||||
|
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
clsx@2.1.1:
|
||||||
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
|
|
||||||
|
|
@ -3535,6 +3606,11 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: 3.x
|
tailwindcss: 3.x
|
||||||
|
|
||||||
|
tailwindcss-animate@1.0.7:
|
||||||
|
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>=3.0.0 || insiders'
|
||||||
|
|
||||||
tailwindcss@3.4.10:
|
tailwindcss@3.4.10:
|
||||||
resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==}
|
resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
@ -4475,6 +4551,18 @@ snapshots:
|
||||||
'@types/react': 18.2.74
|
'@types/react': 18.2.74
|
||||||
'@types/react-dom': 18.2.23
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
|
'@radix-ui/react-collection@1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-slot': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.2.74
|
||||||
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
'@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.74)(react@18.3.1)':
|
'@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.74)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.6
|
'@babel/runtime': 7.25.6
|
||||||
|
|
@ -4523,6 +4611,12 @@ snapshots:
|
||||||
'@types/react': 18.2.74
|
'@types/react': 18.2.74
|
||||||
'@types/react-dom': 18.2.23
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
|
'@radix-ui/react-direction@1.1.0(@types/react@18.2.74)(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.2.74
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.0
|
'@radix-ui/primitive': 1.1.0
|
||||||
|
|
@ -4654,6 +4748,23 @@ snapshots:
|
||||||
'@types/react': 18.2.74
|
'@types/react': 18.2.74
|
||||||
'@types/react-dom': 18.2.23
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.0
|
||||||
|
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-context': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.2.74
|
||||||
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.0.2(@types/react@18.2.74)(react@18.3.1)':
|
'@radix-ui/react-slot@1.0.2(@types/react@18.2.74)(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.6
|
'@babel/runtime': 7.25.6
|
||||||
|
|
@ -4684,6 +4795,22 @@ snapshots:
|
||||||
'@types/react': 18.2.74
|
'@types/react': 18.2.74
|
||||||
'@types/react-dom': 18.2.23
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
|
'@radix-ui/react-tabs@1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.0
|
||||||
|
'@radix-ui/react-context': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-direction': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-id': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.74)(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.2.74
|
||||||
|
'@types/react-dom': 18.2.23
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.1.2(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-tooltip@1.1.2(@types/react-dom@18.2.23)(@types/react@18.2.74)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.0
|
'@radix-ui/primitive': 1.1.0
|
||||||
|
|
@ -5637,8 +5764,16 @@ snapshots:
|
||||||
|
|
||||||
cjs-module-lexer@1.4.0: {}
|
cjs-module-lexer@1.4.0: {}
|
||||||
|
|
||||||
|
class-variance-authority@0.7.0:
|
||||||
|
dependencies:
|
||||||
|
clsx: 2.0.0
|
||||||
|
|
||||||
client-only@0.0.1: {}
|
client-only@0.0.1: {}
|
||||||
|
|
||||||
|
clsx@2.0.0: {}
|
||||||
|
|
||||||
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
color-convert@1.9.3:
|
color-convert@1.9.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.3
|
color-name: 1.1.3
|
||||||
|
|
@ -5937,7 +6072,7 @@ snapshots:
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
|
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
|
||||||
eslint-plugin-react: 7.35.0(eslint@8.57.0)
|
eslint-plugin-react: 7.35.0(eslint@8.57.0)
|
||||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
|
||||||
|
|
@ -5968,7 +6103,7 @@ snapshots:
|
||||||
is-bun-module: 1.1.0
|
is-bun-module: 1.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@typescript-eslint/parser'
|
- '@typescript-eslint/parser'
|
||||||
- eslint-import-resolver-node
|
- eslint-import-resolver-node
|
||||||
|
|
@ -5986,7 +6121,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
|
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
array.prototype.findlastindex: 1.2.5
|
array.prototype.findlastindex: 1.2.5
|
||||||
|
|
@ -7578,6 +7713,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.10
|
tailwindcss: 3.4.10
|
||||||
|
|
||||||
|
tailwindcss-animate@1.0.7(tailwindcss@3.4.10):
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 3.4.10
|
||||||
|
|
||||||
tailwindcss@3.4.10:
|
tailwindcss@3.4.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@alloc/quick-lru': 5.2.0
|
'@alloc/quick-lru': 5.2.0
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ export async function getCollectionById(
|
||||||
|
|
||||||
export async function getOrgCollections(
|
export async function getOrgCollections(
|
||||||
org_id: string,
|
org_id: string,
|
||||||
access_token: string,
|
access_token?: string,
|
||||||
next: any
|
next?: any
|
||||||
) {
|
) {
|
||||||
const result: any = await fetch(
|
const result: any = await fetch(
|
||||||
`${getAPIUrl()}collections/org/${org_id}/page/1/limit/10`,
|
`${getAPIUrl()}collections/org/${org_id}/page/1/limit/10`,
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ import {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function getOrgCourses(
|
export async function getOrgCourses(
|
||||||
org_id: number,
|
org_slug: string,
|
||||||
next: any,
|
next: any,
|
||||||
access_token: any
|
access_token?: any
|
||||||
) {
|
) {
|
||||||
const result: any = await fetch(
|
const result: any = await fetch(
|
||||||
`${getAPIUrl()}courses/org_slug/${org_id}/page/1/limit/10`,
|
`${getAPIUrl()}courses/org_slug/${org_slug}/page/1/limit/10`,
|
||||||
RequestBodyWithAuthHeader('GET', null, next, access_token)
|
RequestBodyWithAuthHeader('GET', null, next, access_token)
|
||||||
)
|
)
|
||||||
const res = await errorHandling(result)
|
const res = await errorHandling(result)
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,8 @@ export function getOrgLogoMediaDirectory(orgUUID: string, fileId: string) {
|
||||||
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/logos/${fileId}`
|
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/logos/${fileId}`
|
||||||
return uri
|
return uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOrgThumbnailMediaDirectory(orgUUID: string, fileId: string) {
|
||||||
|
let uri = `${getMediaUrl()}content/orgs/${orgUUID}/thumbnails/${fileId}`
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,19 @@ export async function uploadOrganizationLogo(
|
||||||
const res = await errorHandling(result)
|
const res = await errorHandling(result)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function uploadOrganizationThumbnail(
|
||||||
|
org_id: string,
|
||||||
|
thumbnail_file: any,
|
||||||
|
access_token: string
|
||||||
|
) {
|
||||||
|
// Send file thumbnail as form data
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('thumbnail_file', thumbnail_file)
|
||||||
|
const result: any = await fetch(
|
||||||
|
`${getAPIUrl()}orgs/` + org_id + '/thumbnail',
|
||||||
|
RequestBodyFormWithAuthHeader('PUT', formData, null, access_token)
|
||||||
|
)
|
||||||
|
const res = await errorHandling(result)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,3 +108,62 @@ a {
|
||||||
transform: translateY(-20px);
|
transform: translateY(-20px);
|
||||||
transition: opacity 300ms, transform 300ms;
|
transition: opacity 300ms, transform 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
--radius: 0.5rem;}
|
||||||
|
.dark {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;}}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;}}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,64 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
darkMode: ['class'],
|
||||||
|
content: [
|
||||||
'./app/**/*.{js,jsx,ts,tsx}',
|
'./app/**/*.{js,jsx,ts,tsx}',
|
||||||
'./components/**/*.{js,jsx,ts,tsx}',
|
'./components/**/*.{js,jsx,ts,tsx}',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require('tailwind-scrollbar')({ nocompatible: true }),
|
require('tailwind-scrollbar')({ nocompatible: true }),
|
||||||
],
|
require("tailwindcss-animate")
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
"@images/*": ["public/img/*"],
|
"@images/*": ["public/img/*"],
|
||||||
"@styles/*": ["styles/*"],
|
"@styles/*": ["styles/*"],
|
||||||
"@services/*": ["services/*"],
|
"@services/*": ["services/*"],
|
||||||
"@editor/*": ["components/Objects/Editor/*"]
|
"@editor/*": ["components/Objects/Editor/*"],
|
||||||
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue