diff --git a/apps/api/src/routers/orgs.py b/apps/api/src/routers/orgs.py
index e3cdc735..737bf2ce 100644
--- a/apps/api/src/routers/orgs.py
+++ b/apps/api/src/routers/orgs.py
@@ -41,6 +41,7 @@ from src.services.orgs.orgs import (
update_org_signup_mechanism,
update_org_thumbnail,
update_org_landing,
+ upload_org_landing_content_service,
)
@@ -428,3 +429,23 @@ async def api_update_org_landing(
Update organization landing object
"""
return await update_org_landing(request, landing_object, org_id, current_user, db_session)
+
+
+@router.post("/{org_id}/landing/content")
+async def api_upload_org_landing_content(
+ request: Request,
+ org_id: int,
+ content_file: UploadFile,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session: Session = Depends(get_db_session),
+):
+ """
+ Upload content for organization landing page
+ """
+ return await upload_org_landing_content_service(
+ request=request,
+ content_file=content_file,
+ org_id=org_id,
+ current_user=current_user,
+ db_session=db_session,
+ )
diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py
index c66e80b3..9f580b28 100644
--- a/apps/api/src/services/orgs/orgs.py
+++ b/apps/api/src/services/orgs/orgs.py
@@ -36,7 +36,7 @@ from src.db.organizations import (
)
from fastapi import HTTPException, UploadFile, status, Request
-from src.services.orgs.uploads import upload_org_logo, upload_org_preview, upload_org_thumbnail
+from src.services.orgs.uploads import upload_org_logo, upload_org_preview, upload_org_thumbnail, upload_org_landing_content
async def get_organization(
@@ -765,6 +765,35 @@ async def update_org_landing(
return {"detail": "Landing object updated"}
+async def upload_org_landing_content_service(
+ request: Request,
+ content_file: UploadFile,
+ org_id: int,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+) -> dict:
+ 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 content
+ name_in_disk = await upload_org_landing_content(content_file, org.org_uuid)
+
+ return {
+ "detail": "Landing content uploaded successfully",
+ "filename": name_in_disk
+ }
+
## 🔒 RBAC Utils ##
diff --git a/apps/api/src/services/orgs/uploads.py b/apps/api/src/services/orgs/uploads.py
index 7c393d53..83296fd7 100644
--- a/apps/api/src/services/orgs/uploads.py
+++ b/apps/api/src/services/orgs/uploads.py
@@ -1,4 +1,6 @@
from uuid import uuid4
+from fastapi import UploadFile
+from fastapi import HTTPException
from src.services.utils.upload_content import upload_content
@@ -45,4 +47,23 @@ async def upload_org_preview(file, org_uuid: str) -> str:
name_in_disk,
)
+ return name_in_disk
+
+
+async def upload_org_landing_content(file: UploadFile, org_uuid: str) -> str:
+ if not file or not file.filename:
+ raise HTTPException(status_code=400, detail="No file provided or invalid filename")
+
+ contents = file.file.read()
+ name_in_disk = f"{uuid4()}.{file.filename.split('.')[-1]}"
+
+ await upload_content(
+ "landing",
+ "orgs",
+ org_uuid,
+ contents,
+ name_in_disk,
+ ["jpg", "jpeg", "png", "gif", "webp", "mp4", "webm", "pdf"] # Common web content formats
+ )
+
return name_in_disk
\ No newline at end of file
diff --git a/apps/web/components/Dashboard/Pages/Org/OrgEditLanding/OrgEditLanding.tsx b/apps/web/components/Dashboard/Pages/Org/OrgEditLanding/OrgEditLanding.tsx
index b218fa49..24bb806c 100644
--- a/apps/web/components/Dashboard/Pages/Org/OrgEditLanding/OrgEditLanding.tsx
+++ b/apps/web/components/Dashboard/Pages/Org/OrgEditLanding/OrgEditLanding.tsx
@@ -10,7 +10,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Button } from "@components/ui/button"
import { useOrg } from '@components/Contexts/OrgContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
-import { updateOrgLanding } from '@services/organizations/orgs'
+import { updateOrgLanding, uploadLandingContent } from '@services/organizations/orgs'
+import { getOrgLandingMediaDirectory } from '@services/media/media'
import { getOrgCourses } from '@services/courses/courses'
import toast from 'react-hot-toast'
import useSWR from 'swr'
@@ -273,7 +274,7 @@ const OrgEditLanding = () => {
{/* Enable/Disable Landing Page */}
-
Landing Page
+
Landing Page
BETA
Customize your organization's landing page
@@ -954,6 +955,64 @@ const HeroSectionEditor: React.FC<{
)
}
+interface ImageUploaderProps {
+ onImageUploaded: (imageUrl: string) => void
+ className?: string
+ buttonText?: string
+ id: string
+}
+
+const ImageUploader: React.FC
= ({ onImageUploaded, className, buttonText = "Upload Image", id }) => {
+ const org = useOrg() as any
+ const session = useLHSession() as any
+ const access_token = session?.data?.tokens?.access_token
+ const [isUploading, setIsUploading] = React.useState(false)
+ const inputId = `imageUpload-${id}`
+
+ const handleFileChange = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0]
+ if (!file) return
+
+ setIsUploading(true)
+ try {
+ const response = await uploadLandingContent(org.id, file, access_token)
+ if (response.status === 200) {
+ const imageUrl = getOrgLandingMediaDirectory(org.org_uuid, response.data.filename)
+ onImageUploaded(imageUrl)
+ toast.success('Image uploaded successfully')
+ } else {
+ toast.error('Failed to upload image')
+ }
+ } catch (error) {
+ console.error('Error uploading image:', error)
+ toast.error('Failed to upload image')
+ } finally {
+ setIsUploading(false)
+ }
+ }
+
+ return (
+
+
+
+
+ )
+}
+
const TextAndImageSectionEditor: React.FC<{
section: LandingTextAndImageSection
onChange: (section: LandingTextAndImageSection) => void
@@ -1010,7 +1069,7 @@ const TextAndImageSectionEditor: React.FC<{
-
+ {section.image.url && (
+
+

+
+ )}
@@ -1064,24 +1140,44 @@ const LogosSectionEditor: React.FC<{
{section.logos.map((logo, index) => (
-
{
- const newLogos = [...section.logos]
- newLogos[index] = { ...logo, url: e.target.value }
- onChange({ ...section, logos: newLogos })
- }}
- placeholder="Logo URL"
- />
-
{
- const newLogos = [...section.logos]
- newLogos[index] = { ...logo, alt: e.target.value }
- onChange({ ...section, logos: newLogos })
- }}
- placeholder="Alt text"
- />
+
+ {
+ const newLogos = [...section.logos]
+ newLogos[index] = { ...logo, url: e.target.value }
+ onChange({ ...section, logos: newLogos })
+ }}
+ placeholder="Logo URL"
+ />
+ {
+ const newLogos = [...section.logos]
+ newLogos[index] = { ...section.logos[index], url }
+ onChange({ ...section, logos: newLogos })
+ }}
+ buttonText="Upload Logo"
+ />
+
+
+
{
+ const newLogos = [...section.logos]
+ newLogos[index] = { ...logo, alt: e.target.value }
+ onChange({ ...section, logos: newLogos })
+ }}
+ placeholder="Alt text"
+ />
+ {logo.url && (
+

+ )}
+