From 41a11c5f83cb6be3256200e7dae218e6c35806c9 Mon Sep 17 00:00:00 2001 From: swve Date: Thu, 26 Sep 2024 23:29:05 +0200 Subject: [PATCH 1/4] feat: init sitemaps generation --- apps/web/app/api/sitemap/route.ts | 61 ++++++++++++++++++++++++++++ apps/web/middleware.ts | 24 +++++++++++ apps/web/services/courses/courses.ts | 6 +-- 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 apps/web/app/api/sitemap/route.ts diff --git a/apps/web/app/api/sitemap/route.ts b/apps/web/app/api/sitemap/route.ts new file mode 100644 index 00000000..b457ec29 --- /dev/null +++ b/apps/web/app/api/sitemap/route.ts @@ -0,0 +1,61 @@ +import { getUriWithOrg } from '@services/config/config'; +import { getOrgCourses } from '@services/courses/courses'; +import { getOrganizationContextInfo } from '@services/organizations/orgs'; +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 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' }, + ...courses.map((course: { course_uuid: string }) => ({ + loc: `${baseUrl}course/${course.course_uuid.replace('course_', '')}`, + priority: 0.8, + 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 }) => ` + + ${loc} + ${priority.toFixed(1)} + ${changefreq} + `).join(''); + + return ` + + ${urlEntries} + `; + } + diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 7ce10397..32ce38d6 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -21,6 +21,7 @@ export const config = { * 5. all root files inside /public (e.g. /favicon.ico) */ '/((?!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 if (hosting_mode === 'multi') { // Get the organization slug from the URL diff --git a/apps/web/services/courses/courses.ts b/apps/web/services/courses/courses.ts index e3f6bd1d..ba6a1e90 100644 --- a/apps/web/services/courses/courses.ts +++ b/apps/web/services/courses/courses.ts @@ -12,12 +12,12 @@ import { */ export async function getOrgCourses( - org_id: number, + org_slug: string, next: any, - access_token: any + access_token?: any ) { 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) ) const res = await errorHandling(result) From aecf03404feeb18db6f4d6a3512a467fc00975d3 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 27 Sep 2024 11:03:33 +0200 Subject: [PATCH 2/4] feat: init org thumbnails creation & edition & redesign dash org config page --- ...40ccb1d456e_add_thumbnail_image_to_orgs.py | 30 ++ apps/api/src/db/organizations.py | 1 + apps/api/src/routers/orgs.py | 23 +- apps/api/src/services/orgs/orgs.py | 39 ++- .../services/orgs/{logos.py => uploads.py} | 15 + .../dash/org/settings/[subpage]/page.tsx | 27 +- apps/web/components.json | 20 ++ .../Org/OrgEditGeneral/OrgEditGeneral.tsx | 256 ++++++++++++------ apps/web/components/ui/tabs.tsx | 55 ++++ apps/web/lib/utils.ts | 6 + apps/web/package.json | 4 + apps/web/pnpm-lock.yaml | 145 +++++++++- apps/web/services/media/media.ts | 5 + apps/web/services/settings/org.ts | 16 ++ apps/web/styles/globals.css | 59 ++++ apps/web/tailwind.config.js | 56 +++- apps/web/tsconfig.json | 3 +- 17 files changed, 661 insertions(+), 99 deletions(-) create mode 100644 apps/api/migrations/versions/040ccb1d456e_add_thumbnail_image_to_orgs.py rename apps/api/src/services/orgs/{logos.py => uploads.py} (54%) create mode 100644 apps/web/components.json create mode 100644 apps/web/components/ui/tabs.tsx create mode 100644 apps/web/lib/utils.ts diff --git a/apps/api/migrations/versions/040ccb1d456e_add_thumbnail_image_to_orgs.py b/apps/api/migrations/versions/040ccb1d456e_add_thumbnail_image_to_orgs.py new file mode 100644 index 00000000..14895852 --- /dev/null +++ b/apps/api/migrations/versions/040ccb1d456e_add_thumbnail_image_to_orgs.py @@ -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 ### diff --git a/apps/api/src/db/organizations.py b/apps/api/src/db/organizations.py index 0edf1200..f5702103 100644 --- a/apps/api/src/db/organizations.py +++ b/apps/api/src/db/organizations.py @@ -12,6 +12,7 @@ class OrganizationBase(SQLModel): slug: str email: str logo_image: Optional[str] + thumbnail_image: Optional[str] class Organization(OrganizationBase, table=True): diff --git a/apps/api/src/routers/orgs.py b/apps/api/src/routers/orgs.py index 748b5fa6..5c171f62 100644 --- a/apps/api/src/routers/orgs.py +++ b/apps/api/src/routers/orgs.py @@ -38,6 +38,7 @@ from src.services.orgs.orgs import ( update_org, update_org_logo, update_org_signup_mechanism, + update_org_thumbnail, ) @@ -303,7 +304,7 @@ async def api_update_org_logo( db_session: Session = Depends(get_db_session), ): """ - Get single Org by Slug + Update org logo """ return await update_org_logo( 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}") async def api_user_orgs( request: Request, diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py index 23d1b4e6..c7e92000 100644 --- a/apps/api/src/services/orgs/orgs.py +++ b/apps/api/src/services/orgs/orgs.py @@ -34,9 +34,10 @@ from src.db.organizations import ( OrganizationRead, OrganizationUpdate, ) -from src.services.orgs.logos import upload_org_logo from fastapi import HTTPException, UploadFile, status, Request +from src.services.orgs.uploads import upload_org_logo, upload_org_thumbnail + async def get_organization( request: Request, @@ -421,6 +422,42 @@ async def update_org_logo( 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( request: Request, diff --git a/apps/api/src/services/orgs/logos.py b/apps/api/src/services/orgs/uploads.py similarity index 54% rename from apps/api/src/services/orgs/logos.py rename to apps/api/src/services/orgs/uploads.py index 9bb173d3..813b625d 100644 --- a/apps/api/src/services/orgs/logos.py +++ b/apps/api/src/services/orgs/uploads.py @@ -16,3 +16,18 @@ async def upload_org_logo(logo_file, org_uuid): ) 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 diff --git a/apps/web/app/orgs/[orgslug]/dash/org/settings/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/org/settings/[subpage]/page.tsx index ee6a9479..d748809b 100644 --- a/apps/web/app/orgs/[orgslug]/dash/org/settings/[subpage]/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/org/settings/[subpage]/page.tsx @@ -3,7 +3,7 @@ import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import { getUriWithOrg } from '@services/config/config' import { Info } from 'lucide-react' import Link from 'next/link' -import React from 'react' +import React, { useEffect } from 'react' import { motion } from 'framer-motion' import OrgEditGeneral from '@components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral' @@ -13,14 +13,31 @@ export type 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 (
-
-
-
- Organization Settings +
+
+
+ {H1Label} +
+
+ {H2Label}{' '}
diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 00000000..7a2c084c --- /dev/null +++ b/apps/web/components.json @@ -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" + } +} \ No newline at end of file diff --git a/apps/web/components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral.tsx b/apps/web/components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral.tsx index 118eec41..ef26f827 100644 --- a/apps/web/components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral.tsx +++ b/apps/web/components/Dashboard/Org/OrgEditGeneral/OrgEditGeneral.tsx @@ -4,12 +4,16 @@ import { Field, Form, Formik } from 'formik' import { updateOrganization, uploadOrganizationLogo, + uploadOrganizationThumbnail, } 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 { useRouter } from 'next/navigation' import { useOrg } from '@components/Contexts/OrgContext' 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 { name: string @@ -17,32 +21,54 @@ interface OrganizationValues { slug: string logo: string email: string + thumbnail: string } -function OrgEditGeneral(props: any) { - const [selectedFile, setSelectedFile] = useState(null) - const router = useRouter(); - const session = useLHSession() as any; - const access_token = session?.data?.tokens?.access_token; +function OrgEditGeneral() { + const router = useRouter() + const session = useLHSession() as any + const access_token = session?.data?.tokens?.access_token const org = useOrg() as any - // ... + const [selectedTab, setSelectedTab] = useState<'logo' | 'thumbnail'>('logo'); + const [localLogo, setLocalLogo] = useState(null); + const [localThumbnail, setLocalThumbnail] = useState(null); - const handleFileChange = (event: React.ChangeEvent) => { + const handleFileChange = async (event: React.ChangeEvent) => { if (event.target.files && event.target.files.length > 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 () => { - if (selectedFile) { - let org_id = org.id - await uploadOrganizationLogo(org_id, selectedFile, access_token) - setSelectedFile(null) // Reset the selected file - await revalidateTags(['organizations'], org.slug) - router.refresh() + const handleThumbnailChange = async (event: React.ChangeEvent) => { + if (event.target.files && event.target.files.length > 0) { + const file = event.target.files[0]; + setLocalThumbnail(URL.createObjectURL(file)); + const loadingToast = toast.loading('Uploading thumbnail...'); + try { + 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 = { name: org?.name, @@ -50,21 +76,25 @@ function OrgEditGeneral(props: any) { slug: org?.slug, logo: org?.logo, email: org?.email, + thumbnail: org?.thumbnail, } const updateOrg = async (values: OrganizationValues) => { - let org_id = org.id - await updateOrganization(org_id, values, access_token) - - // Mutate the org - await revalidateTags(['organizations'], org.slug) - router.refresh() + const loadingToast = toast.loading('Updating organization...'); + try { + await updateOrganization(org.id, values, access_token) + await revalidateTags(['organizations'], org.slug) + toast.success('Organization Updated', { id: loadingToast }); + } catch (err) { + toast.error('Failed to update organization', { id: loadingToast }); + } } - useEffect(() => { }, [org]) + useEffect(() => {}, [org]) return (
+ {({ isSubmitting }) => (
- - +
+
+ + - - + + - + + -
- - + + + + +
+ +
+ + + Logo + Thumbnail + + +
+
+
+
+
+
+ + +
+
+
+ +

Accepts PNG , JPG

+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ +

Accepts PNG, JPG

+
+
+ + +
- - - - - - - - )} diff --git a/apps/web/components/ui/tabs.tsx b/apps/web/components/ui/tabs.tsx new file mode 100644 index 00000000..0f4caebb --- /dev/null +++ b/apps/web/components/ui/tabs.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts new file mode 100644 index 00000000..bd0c391d --- /dev/null +++ b/apps/web/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/web/package.json b/apps/web/package.json index b15412c1..961f7289 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,6 +18,7 @@ "@radix-ui/react-form": "^0.0.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", "@sentry/nextjs": "^8.27.0", "@stitches/react": "^1.2.8", @@ -32,6 +33,8 @@ "@tiptap/starter-kit": "^2.6.6", "@types/randomcolor": "^0.5.9", "avvvatars-react": "^0.4.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "dayjs": "^1.11.13", "formik": "^2.4.6", "framer-motion": "^10.18.0", @@ -59,6 +62,7 @@ "swr": "^2.2.5", "tailwind-merge": "^2.5.2", "tailwind-scrollbar": "^3.1.0", + "tailwindcss-animate": "^1.0.7", "uuid": "^9.0.1", "y-indexeddb": "^9.0.12", "y-prosemirror": "^1.2.12", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 587ec875..822c52c4 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@radix-ui/react-switch': 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-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': 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) @@ -71,6 +74,12 @@ importers: avvvatars-react: 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) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -152,6 +161,9 @@ importers: tailwind-scrollbar: specifier: ^3.1.0 version: 3.1.0(tailwindcss@3.4.10) + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.10) uuid: specifier: ^9.0.1 version: 9.0.1 @@ -799,6 +811,19 @@ packages: '@types/react-dom': 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': resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -848,6 +873,15 @@ packages: '@types/react-dom': 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': resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} peerDependencies: @@ -997,6 +1031,19 @@ packages: '@types/react-dom': 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': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -1028,6 +1075,19 @@ packages: '@types/react-dom': 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': resolution: {integrity: sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==} peerDependencies: @@ -1817,9 +1877,20 @@ packages: cjs-module-lexer@1.4.0: resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + client-only@0.0.1: 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: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -3535,6 +3606,11 @@ packages: peerDependencies: tailwindcss: 3.x + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.10: resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==} engines: {node: '>=14.0.0'} @@ -4475,6 +4551,18 @@ snapshots: '@types/react': 18.2.74 '@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)': dependencies: '@babel/runtime': 7.25.6 @@ -4523,6 +4611,12 @@ snapshots: '@types/react': 18.2.74 '@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)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -4654,6 +4748,23 @@ snapshots: '@types/react': 18.2.74 '@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)': dependencies: '@babel/runtime': 7.25.6 @@ -4684,6 +4795,22 @@ snapshots: '@types/react': 18.2.74 '@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)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -5637,8 +5764,16 @@ snapshots: cjs-module-lexer@1.4.0: {} + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + client-only@0.0.1: {} + clsx@2.0.0: {} + + clsx@2.1.1: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -5937,7 +6072,7 @@ snapshots: eslint: 8.57.0 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-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-react: 7.35.0(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-glob: 4.0.3 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: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -5986,7 +6121,7 @@ snapshots: transitivePeerDependencies: - 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: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -7578,6 +7713,10 @@ snapshots: dependencies: tailwindcss: 3.4.10 + tailwindcss-animate@1.0.7(tailwindcss@3.4.10): + dependencies: + tailwindcss: 3.4.10 + tailwindcss@3.4.10: dependencies: '@alloc/quick-lru': 5.2.0 diff --git a/apps/web/services/media/media.ts b/apps/web/services/media/media.ts index fb1c969f..1a45d2af 100644 --- a/apps/web/services/media/media.ts +++ b/apps/web/services/media/media.ts @@ -91,3 +91,8 @@ export function getOrgLogoMediaDirectory(orgUUID: string, fileId: string) { let uri = `${getMediaUrl()}content/orgs/${orgUUID}/logos/${fileId}` return uri } + +export function getOrgThumbnailMediaDirectory(orgUUID: string, fileId: string) { + let uri = `${getMediaUrl()}content/orgs/${orgUUID}/thumbnails/${fileId}` + return uri +} diff --git a/apps/web/services/settings/org.ts b/apps/web/services/settings/org.ts index c74450eb..bcceb138 100644 --- a/apps/web/services/settings/org.ts +++ b/apps/web/services/settings/org.ts @@ -38,3 +38,19 @@ export async function uploadOrganizationLogo( const res = await errorHandling(result) 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 +} diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index 6b8d69b3..77b4a6e0 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -108,3 +108,62 @@ a { transform: translateY(-20px); 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;}} diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 1bb1b1aa..49516b96 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -1,14 +1,64 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ + darkMode: ['class'], + content: [ './app/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}', ], 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: [ require('tailwind-scrollbar')({ nocompatible: true }), - ], + require("tailwindcss-animate") +], } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 6546f796..aa2bce06 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -25,7 +25,8 @@ "@images/*": ["public/img/*"], "@styles/*": ["styles/*"], "@services/*": ["services/*"], - "@editor/*": ["components/Objects/Editor/*"] + "@editor/*": ["components/Objects/Editor/*"], + "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/**/*.tsx", ".next/types/**/*.ts"], From 96af67f9aaed7c15d29af6c04036e7de761d8cd7 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 27 Sep 2024 18:28:28 +0200 Subject: [PATCH 3/4] feat: add org thumbnails on app pages --- .../[orgslug]/(withmenu)/collections/page.tsx | 9 ++ .../[orgslug]/(withmenu)/courses/page.tsx | 9 ++ .../app/orgs/[orgslug]/(withmenu)/page.tsx | 9 ++ apps/web/app/page.tsx | 95 ------------------- 4 files changed, 27 insertions(+), 95 deletions(-) delete mode 100644 apps/web/app/page.tsx diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx index 9ff96216..b69b3cc7 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/collections/page.tsx @@ -11,6 +11,7 @@ import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder' import { nextAuthOptions } from 'app/auth/options' import { getServerSession } from 'next-auth' import { getOrgCollections } from '@services/courses/collections' +import { getOrgThumbnailMediaDirectory } from '@services/media/media' type MetadataProps = { params: { orgslug: string; courseid: string } @@ -44,6 +45,14 @@ export async function generateMetadata({ title: `Collections — ${org.name}`, description: `Collections of courses from ${org.name}`, type: 'website', + images: [ + { + url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image), + width: 800, + height: 600, + alt: org.name, + }, + ], }, } } diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx index a339cd02..477b8ba6 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/courses/page.tsx @@ -5,6 +5,7 @@ import { getOrganizationContextInfo } from '@services/organizations/orgs' import { nextAuthOptions } from 'app/auth/options' import { getServerSession } from 'next-auth' import { getOrgCourses } from '@services/courses/courses' +import { getOrgThumbnailMediaDirectory } from '@services/media/media' type MetadataProps = { params: { orgslug: string } @@ -39,6 +40,14 @@ export async function generateMetadata({ title: 'Courses — ' + org.name, description: org.description, type: 'website', + images: [ + { + url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image), + width: 800, + height: 600, + alt: org.name, + }, + ], }, } } diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx index 5718bb98..429b35e2 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/page.tsx @@ -15,6 +15,7 @@ import ContentPlaceHolderIfUserIsNotAdmin from '@components/ContentPlaceHolder' import { getOrgCollections } from '@services/courses/collections' import { getServerSession } from 'next-auth' import { nextAuthOptions } from 'app/auth/options' +import { getOrgThumbnailMediaDirectory } from '@services/media/media' type MetadataProps = { params: { orgslug: string } @@ -48,6 +49,14 @@ export async function generateMetadata({ title: `Home — ${org.name}`, description: org.description, type: 'website', + images: [ + { + url: getOrgThumbnailMediaDirectory(org?.org_uuid, org?.thumbnail_image), + width: 800, + height: 600, + alt: org.name, + }, + ], }, } } diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx deleted file mode 100644 index ea12976d..00000000 --- a/apps/web/app/page.tsx +++ /dev/null @@ -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 ( - - - Learnhouse Icon - -
-
-
-
- -
- - See Organizations - -
-
- - Login - -
-
-
- ) -} - -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; - } -` From 0893e6f9d306f4f8ea8cd9411bc93f7e37626947 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 27 Sep 2024 18:36:21 +0200 Subject: [PATCH 4/4] feat: add more pages to the sitemap --- apps/web/app/api/sitemap/route.ts | 13 ++++++++++++- apps/web/services/courses/collections.ts | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/web/app/api/sitemap/route.ts b/apps/web/app/api/sitemap/route.ts index b457ec29..82a75aba 100644 --- a/apps/web/app/api/sitemap/route.ts +++ b/apps/web/app/api/sitemap/route.ts @@ -1,6 +1,7 @@ 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'; @@ -13,6 +14,7 @@ export async function GET(request: NextRequest) { 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) { @@ -23,9 +25,18 @@ export async function GET(request: NextRequest) { 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.8, + 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' })) ]; diff --git a/apps/web/services/courses/collections.ts b/apps/web/services/courses/collections.ts index 93051fbf..fb67264a 100644 --- a/apps/web/services/courses/collections.ts +++ b/apps/web/services/courses/collections.ts @@ -46,8 +46,8 @@ export async function getCollectionById( export async function getOrgCollections( org_id: string, - access_token: string, - next: any + access_token?: string, + next?: any ) { const result: any = await fetch( `${getAPIUrl()}collections/org/${org_id}/page/1/limit/10`,