'use client' import React from 'react' import { LandingObject, LandingSection, LandingHeroSection, LandingTextAndImageSection, LandingLogos, LandingPeople, LandingBackground, LandingButton, LandingHeading, LandingImage, LandingFeaturedCourses } from './landing_types' import { Plus, Eye, ArrowUpDown, Trash2, GripVertical, LayoutTemplate, ImageIcon, Users, Award, ArrowRight, Edit, Link, Upload, Save, BookOpen } from 'lucide-react' import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' import { Input } from "@components/ui/input" import { Textarea } from "@components/ui/textarea" import { Label } from "@components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/ui/select" import { Button } from "@components/ui/button" import { useOrg } from '@components/Contexts/OrgContext' import { useLHSession } from '@components/Contexts/LHSessionContext' 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' const SECTION_TYPES = { hero: { icon: LayoutTemplate, label: 'Hero', description: 'Add a hero section with heading and call-to-action' }, 'text-and-image': { icon: ImageIcon, label: 'Text & Image', description: 'Add a section with text and an image' }, logos: { icon: Award, label: 'Logos', description: 'Add a section to showcase logos' }, people: { icon: Users, label: 'People', description: 'Add a section to highlight team members' }, 'featured-courses': { icon: BookOpen, label: 'Courses', description: 'Add a section to showcase selected courses' } } as const const PREDEFINED_GRADIENTS = { 'sunrise': { colors: ['#fef9f3', '#ffecd2'] as Array, direction: '45deg' }, 'mint-breeze': { colors: ['#f0fff4', '#dcfce7'] as Array, direction: '45deg' }, 'deep-ocean': { colors: ['#0f172a', '#1e3a8a'] as Array, direction: '135deg' }, 'sunset-blaze': { colors: ['#7f1d1d', '#ea580c'] as Array, direction: '45deg' }, 'midnight-purple': { colors: ['#581c87', '#7e22ce'] as Array, direction: '90deg' }, 'forest-depths': { colors: ['#064e3b', '#059669'] as Array, direction: '225deg' }, 'berry-fusion': { colors: ['#831843', '#be185d'] as Array, direction: '135deg' }, 'cosmic-night': { colors: ['#1e1b4b', '#4338ca'] as Array, direction: '45deg' }, 'autumn-fire': { colors: ['#7c2d12', '#c2410c'] as Array, direction: '90deg' }, 'emerald-depths': { colors: ['#064e3b', '#10b981'] as Array, direction: '135deg' }, 'royal-navy': { colors: ['#1e3a8a', '#3b82f6'] as Array, direction: '225deg' }, 'volcanic': { colors: ['#991b1b', '#f97316'] as Array, direction: '315deg' }, 'arctic-night': { colors: ['#0f172a', '#475569'] as Array, direction: '90deg' }, 'grape-punch': { colors: ['#6b21a8', '#d946ef'] as Array, direction: '135deg' }, 'marine-blue': { colors: ['#0c4a6e', '#0ea5e9'] as Array, direction: '45deg' } } as const const GRADIENT_DIRECTIONS = { '45deg': '↗️ Top Right', '90deg': '⬆️ Top', '135deg': '↖️ Top Left', '180deg': '⬅️ Left', '225deg': '↙️ Bottom Left', '270deg': '⬇️ Bottom', '315deg': '↘️ Bottom Right', '0deg': '➡️ Right' } as const const getSectionDisplayName = (section: LandingSection) => { return SECTION_TYPES[section.type as keyof typeof SECTION_TYPES].label } const OrgEditLanding = () => { const org = useOrg() as any const session = useLHSession() as any const access_token = session?.data?.tokens?.access_token const [isLandingEnabled, setIsLandingEnabled] = React.useState(false) const [landingData, setLandingData] = React.useState({ sections: [], enabled: false }) const [selectedSection, setSelectedSection] = React.useState(null) const [isSaving, setIsSaving] = React.useState(false) // Initialize landing data from org config React.useEffect(() => { if (org?.config?.config?.landing) { const landingConfig = org.config.config.landing setLandingData({ sections: landingConfig.sections || [], enabled: landingConfig.enabled || false }) setIsLandingEnabled(landingConfig.enabled || false) } }, [org]) const addSection = (type: string) => { const newSection: LandingSection = createEmptySection(type) setLandingData(prev => ({ ...prev, sections: [...prev.sections, newSection] })) } const createEmptySection = (type: string): LandingSection => { switch (type) { case 'hero': return { type: 'hero', title: 'New Hero Section', background: { type: 'solid', color: '#ffffff' }, heading: { text: 'Welcome', color: '#000000', size: 'large' }, subheading: { text: 'Start your learning journey', color: '#666666', size: 'medium' }, buttons: [], illustration: undefined, contentAlign: 'center' } case 'text-and-image': return { type: 'text-and-image', title: 'New Text & Image Section', text: 'Add your content here', flow: 'left', image: { url: '', alt: '' }, buttons: [] } case 'logos': return { type: 'logos', title: 'New Logos Section', logos: [] } case 'people': return { type: 'people', title: 'New People Section', people: [] } case 'featured-courses': return { type: 'featured-courses', title: 'Courses', courses: [] } default: throw new Error('Invalid section type') } } const updateSection = (index: number, updatedSection: LandingSection) => { const newSections = [...landingData.sections] newSections[index] = updatedSection setLandingData(prev => ({ ...prev, sections: newSections })) } const deleteSection = (index: number) => { setLandingData(prev => ({ ...prev, sections: prev.sections.filter((_, i) => i !== index) })) setSelectedSection(null) } const onDragEnd = (result: any) => { if (!result.destination) return const items = Array.from(landingData.sections) const [reorderedItem] = items.splice(result.source.index, 1) items.splice(result.destination.index, 0, reorderedItem) setLandingData(prev => ({ ...prev, sections: items })) setSelectedSection(result.destination.index) } const handleSave = async () => { if (!org?.id) { toast.error('Organization ID not found') return } setIsSaving(true) try { const res = await updateOrgLanding(org.id, { sections: landingData.sections, enabled: isLandingEnabled }, access_token) if (res.status === 200) { toast.success('Landing page saved successfully') } else { toast.error('Error saving landing page') } } catch (error) { toast.error('Error saving landing page') console.error('Error saving landing page:', error) } finally { setIsSaving(false) } } return (
{/* Enable/Disable Landing Page */}

Landing Page
BETA

Customize your organization's landing page

{isLandingEnabled && ( <> {/* Section List */}
{/* Sections Panel */}

Sections

{(provided) => (
{landingData.sections.map((section, index) => ( {(provided, snapshot) => (
setSelectedSection(index)} className={`p-4 bg-white/80 backdrop-blur-sm rounded-lg cursor-pointer border ${ selectedSection === index ? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500/20 shadow-sm' : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50/50 hover:shadow-sm' } ${snapshot.isDragging ? 'shadow-lg ring-2 ring-blue-500/20 rotate-2' : ''}`} >
{React.createElement(SECTION_TYPES[section.type as keyof typeof SECTION_TYPES].icon, { size: 16 })}
{getSectionDisplayName(section)}
)}
))} {provided.placeholder}
)}
{/* Editor Panel */}
{selectedSection !== null ? ( updateSection(selectedSection, updatedSection)} /> ) : (
Select a section to edit or add a new one
)}
)}
) } interface SectionEditorProps { section: LandingSection onChange: (section: LandingSection) => void } const SectionEditor: React.FC = ({ section, onChange }) => { switch (section.type) { case 'hero': return case 'text-and-image': return case 'logos': return case 'people': return case 'featured-courses': return default: return
Unknown section type
} } const HeroSectionEditor: React.FC<{ section: LandingHeroSection onChange: (section: LandingHeroSection) => void }> = ({ section, onChange }) => { const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { const reader = new FileReader() reader.onloadend = () => { onChange({ ...section, background: { type: 'image', image: reader.result as string } }) } reader.readAsDataURL(file) } } return (

Hero Section

{/* Title */}
onChange({ ...section, title: e.target.value })} placeholder="Enter section title" />
{/* Background */}
{section.background.type === 'solid' && (
onChange({ ...section, background: { ...section.background, color: e.target.value } })} className="w-20 h-10 p-1" /> onChange({ ...section, background: { ...section.background, color: e.target.value } })} placeholder="#ffffff" className="font-mono" />
)} {section.background.type === 'gradient' && (
{!Object.values(PREDEFINED_GRADIENTS).some( preset => preset.colors[0] === section.background.colors?.[0] && preset.colors[1] === section.background.colors?.[1] ) ? (
onChange({ ...section, background: { ...section.background, colors: [e.target.value, section.background.colors?.[1] || '#f0f0f0'] } })} className="w-20 h-10 p-1" /> onChange({ ...section, background: { ...section.background, colors: [e.target.value, section.background.colors?.[1] || '#f0f0f0'] } })} placeholder="#ffffff" className="font-mono" />
onChange({ ...section, background: { ...section.background, colors: [section.background.colors?.[0] || '#ffffff', e.target.value] } })} className="w-20 h-10 p-1" /> onChange({ ...section, background: { ...section.background, colors: [section.background.colors?.[0] || '#ffffff', e.target.value] } })} placeholder="#f0f0f0" className="font-mono" />
) : (
)}
)} {section.background.type === 'image' && (
{section.background.image && (
Background preview
)}
)}
{/* Heading */}
onChange({ ...section, heading: { ...section.heading, text: e.target.value } })} placeholder="Enter heading text" />
onChange({ ...section, heading: { ...section.heading, color: e.target.value } })} className="w-20 h-10 p-1" /> onChange({ ...section, heading: { ...section.heading, color: e.target.value } })} placeholder="#000000" className="font-mono" />
{/* Subheading */}
onChange({ ...section, subheading: { ...section.subheading, text: e.target.value } })} placeholder="Enter subheading text" />
onChange({ ...section, subheading: { ...section.subheading, color: e.target.value } })} className="w-20 h-10 p-1" /> onChange({ ...section, subheading: { ...section.subheading, color: e.target.value } })} placeholder="#666666" className="font-mono" />
{/* Buttons */}
{section.buttons.map((button, index) => (
{ const newButtons = [...section.buttons] newButtons[index] = { ...button, text: e.target.value } onChange({ ...section, buttons: newButtons }) }} placeholder="Button text" />
{ const newButtons = [...section.buttons] newButtons[index] = { ...button, color: e.target.value } onChange({ ...section, buttons: newButtons }) }} className="w-10 h-8 p-1" /> { const newButtons = [...section.buttons] newButtons[index] = { ...button, background: e.target.value } onChange({ ...section, buttons: newButtons }) }} className="w-10 h-8 p-1" />
{ const newButtons = [...section.buttons] newButtons[index] = { ...button, link: e.target.value } onChange({ ...section, buttons: newButtons }) }} placeholder="Button link" />
))} {section.buttons.length < 2 && ( )}
{/* Illustration */}
{ if (e.target.value) { onChange({ ...section, illustration: { image: { url: e.target.value, alt: section.illustration?.image.alt || '' }, position: 'left', verticalAlign: 'center', size: 'medium' } }) } }} placeholder="Illustration URL" /> { if (section.illustration?.image.url) { onChange({ ...section, illustration: { ...section.illustration, image: { ...section.illustration.image, alt: e.target.value } } }) } }} placeholder="Alt text" /> onChange({ ...section, illustration: { image: { url, alt: section.illustration?.image.alt || '' }, position: 'left', verticalAlign: 'center', size: 'medium' } })} buttonText="Upload Illustration" /> {section.illustration?.image.url && ( {section.illustration?.image.alt} )}
{section.illustration?.image.url && ( )}
) } 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 }> = ({ section, onChange }) => { return (

Text & Image Section

{/* Title */}
onChange({ ...section, title: e.target.value })} placeholder="Enter section title" />
{/* Text */}