mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: dynamic and classic landing page detection
This commit is contained in:
parent
f6f915c956
commit
7b33639b9a
3 changed files with 343 additions and 134 deletions
|
|
@ -16,6 +16,8 @@ import { getOrgCollections } from '@services/courses/collections'
|
|||
import { getServerSession } from 'next-auth'
|
||||
import { nextAuthOptions } from 'app/auth/options'
|
||||
import { getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||
import LandingClassic from '@components/Landings/LandingClassic'
|
||||
import LandingCustom from '@components/Landings/LandingCustom'
|
||||
|
||||
type MetadataProps = {
|
||||
params: { orgslug: string }
|
||||
|
|
@ -71,7 +73,7 @@ const OrgHomePage = async (params: any) => {
|
|||
access_token ? access_token : null
|
||||
)
|
||||
const org = await getOrganizationContextInfo(orgslug, {
|
||||
revalidate: 1800,
|
||||
revalidate: 0,
|
||||
tags: ['organizations'],
|
||||
})
|
||||
const org_id = org.id
|
||||
|
|
@ -81,141 +83,24 @@ const OrgHomePage = async (params: any) => {
|
|||
{ revalidate: 0, tags: ['courses'] }
|
||||
)
|
||||
|
||||
// Check if custom landing is enabled
|
||||
const hasCustomLanding = org.config?.config?.landing?.enabled
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<GeneralWrapperStyled>
|
||||
{/* Collections */}
|
||||
<div className="flex flex-col space-y-4 mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<TypeOfContentTitle title="Collections" type="col" />
|
||||
<AuthenticatedClientElement
|
||||
checkMethod="roles"
|
||||
ressourceType="collections"
|
||||
action="create"
|
||||
orgId={org_id}
|
||||
>
|
||||
<Link href={getUriWithOrg(orgslug, '/collections/new')}>
|
||||
<NewCollectionButton />
|
||||
</Link>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{collections.map((collection: any) => (
|
||||
<div key={collection.collection_id} className="flex flex-col p-3">
|
||||
<CollectionThumbnail
|
||||
collection={collection}
|
||||
orgslug={orgslug}
|
||||
org_id={org.org_id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{collections.length === 0 && (
|
||||
<div className="col-span-full flex justify-center items-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="mb-4">
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 295 295"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mx-auto"
|
||||
>
|
||||
<rect
|
||||
opacity="0.51"
|
||||
x="10"
|
||||
y="10"
|
||||
width="275"
|
||||
height="275"
|
||||
rx="75"
|
||||
stroke="#4B5564"
|
||||
strokeOpacity="0.15"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z"
|
||||
fill="#4B5564"
|
||||
fillOpacity="0.08"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-gray-600 mb-2">
|
||||
No collections yet
|
||||
</h1>
|
||||
<p className="text-md text-gray-400">
|
||||
<ContentPlaceHolderIfUserIsNotAdmin
|
||||
text="Create collections to group courses together"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Courses */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<TypeOfContentTitle title="Courses" type="cou" />
|
||||
<AuthenticatedClientElement
|
||||
ressourceType="courses"
|
||||
action="create"
|
||||
checkMethod="roles"
|
||||
orgId={org_id}
|
||||
>
|
||||
<Link href={getUriWithOrg(orgslug, '/courses?new=true')}>
|
||||
<NewCourseButton />
|
||||
</Link>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{courses.map((course: any) => (
|
||||
<div key={course.course_uuid} className="p-3">
|
||||
<CourseThumbnail course={course} orgslug={orgslug} />
|
||||
</div>
|
||||
))}
|
||||
{courses.length === 0 && (
|
||||
<div className="col-span-full flex justify-center items-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 ">
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
className="mx-auto"
|
||||
viewBox="0 0 295 295"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
opacity="0.51"
|
||||
x="10"
|
||||
y="10"
|
||||
width="275"
|
||||
height="275"
|
||||
rx="75"
|
||||
stroke="#4B5564"
|
||||
strokeOpacity="0.15"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z"
|
||||
fill="#4B5564"
|
||||
fillOpacity="0.08"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-gray-600 mb-2">
|
||||
No courses yet
|
||||
</h1>
|
||||
<p className="text-md text-gray-400">
|
||||
<ContentPlaceHolderIfUserIsNotAdmin text='Create courses to add content' />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</GeneralWrapperStyled>
|
||||
{hasCustomLanding ? (
|
||||
<LandingCustom
|
||||
landing={org.config.config.landing}
|
||||
orgslug={orgslug}
|
||||
/>
|
||||
) : (
|
||||
<LandingClassic
|
||||
courses={courses}
|
||||
collections={collections}
|
||||
orgslug={orgslug}
|
||||
org_id={org_id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
160
apps/web/components/Landings/LandingClassic.tsx
Normal file
160
apps/web/components/Landings/LandingClassic.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import React from 'react'
|
||||
import GeneralWrapperStyled from '@components/Objects/StyledElements/Wrappers/GeneralWrapper'
|
||||
import TypeOfContentTitle from '@components/Objects/StyledElements/Titles/TypeOfContentTitle'
|
||||
import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail'
|
||||
import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail'
|
||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
|
||||
import NewCourseButton from '@components/Objects/StyledElements/Buttons/NewCourseButton'
|
||||
import NewCollectionButton from '@components/Objects/StyledElements/Buttons/NewCollectionButton'
|
||||
import ContentPlaceHolderIfUserIsNotAdmin from '@components/Objects/ContentPlaceHolder'
|
||||
import Link from 'next/link'
|
||||
import { getUriWithOrg } from '@services/config/config'
|
||||
|
||||
interface LandingClassicProps {
|
||||
courses: any[]
|
||||
collections: any[]
|
||||
orgslug: string
|
||||
org_id: string
|
||||
}
|
||||
|
||||
function LandingClassic({ courses, collections, orgslug, org_id }: LandingClassicProps) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<GeneralWrapperStyled>
|
||||
{/* Collections */}
|
||||
<div className="flex flex-col space-y-4 mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<TypeOfContentTitle title="Collections" type="col" />
|
||||
<AuthenticatedClientElement
|
||||
checkMethod="roles"
|
||||
ressourceType="collections"
|
||||
action="create"
|
||||
orgId={org_id}
|
||||
>
|
||||
<Link href={getUriWithOrg(orgslug, '/collections/new')}>
|
||||
<NewCollectionButton />
|
||||
</Link>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{collections.map((collection: any) => (
|
||||
<div key={collection.collection_id} className="flex flex-col p-3">
|
||||
<CollectionThumbnail
|
||||
collection={collection}
|
||||
orgslug={orgslug}
|
||||
org_id={org_id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{collections.length === 0 && (
|
||||
<div className="col-span-full flex justify-center items-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="mb-4">
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 295 295"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mx-auto"
|
||||
>
|
||||
<rect
|
||||
opacity="0.51"
|
||||
x="10"
|
||||
y="10"
|
||||
width="275"
|
||||
height="275"
|
||||
rx="75"
|
||||
stroke="#4B5564"
|
||||
strokeOpacity="0.15"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z"
|
||||
fill="#4B5564"
|
||||
fillOpacity="0.08"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-gray-600 mb-2">
|
||||
No collections yet
|
||||
</h1>
|
||||
<p className="text-md text-gray-400">
|
||||
<ContentPlaceHolderIfUserIsNotAdmin
|
||||
text="Create collections to group courses together"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Courses */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<TypeOfContentTitle title="Courses" type="cou" />
|
||||
<AuthenticatedClientElement
|
||||
ressourceType="courses"
|
||||
action="create"
|
||||
checkMethod="roles"
|
||||
orgId={org_id}
|
||||
>
|
||||
<Link href={getUriWithOrg(orgslug, '/courses?new=true')}>
|
||||
<NewCourseButton />
|
||||
</Link>
|
||||
</AuthenticatedClientElement>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{courses.map((course: any) => (
|
||||
<div key={course.course_uuid} className="p-3">
|
||||
<CourseThumbnail course={course} orgslug={orgslug} />
|
||||
</div>
|
||||
))}
|
||||
{courses.length === 0 && (
|
||||
<div className="col-span-full flex justify-center items-center py-8">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 ">
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
className="mx-auto"
|
||||
viewBox="0 0 295 295"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
opacity="0.51"
|
||||
x="10"
|
||||
y="10"
|
||||
width="275"
|
||||
height="275"
|
||||
rx="75"
|
||||
stroke="#4B5564"
|
||||
strokeOpacity="0.15"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
d="M135.8 200.8V130L122.2 114.6L135.8 110.4V102.8L122.2 87.4L159.8 76V200.8L174.6 218H121L135.8 200.8Z"
|
||||
fill="#4B5564"
|
||||
fillOpacity="0.08"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-gray-600 mb-2">
|
||||
No courses yet
|
||||
</h1>
|
||||
<p className="text-md text-gray-400">
|
||||
<ContentPlaceHolderIfUserIsNotAdmin text='Create courses to add content' />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</GeneralWrapperStyled>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingClassic
|
||||
164
apps/web/components/Landings/LandingCustom.tsx
Normal file
164
apps/web/components/Landings/LandingCustom.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import React from 'react'
|
||||
import { LandingSection } from '@components/Dashboard/Pages/Org/OrgEditLanding/landing_types'
|
||||
|
||||
interface LandingCustomProps {
|
||||
landing: {
|
||||
sections: LandingSection[]
|
||||
enabled: boolean
|
||||
}
|
||||
orgslug: string
|
||||
}
|
||||
|
||||
function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||
const renderSection = (section: LandingSection) => {
|
||||
switch (section.type) {
|
||||
case 'hero':
|
||||
return (
|
||||
<div
|
||||
key={`hero-${section.title}`}
|
||||
className="min-h-[500px] flex items-center justify-center"
|
||||
style={{
|
||||
background: section.background.type === 'solid'
|
||||
? section.background.color
|
||||
: section.background.type === 'gradient'
|
||||
? `linear-gradient(${section.background.direction || '45deg'}, ${section.background.colors?.join(', ')})`
|
||||
: `url(${section.background.image}) center/cover`
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<h1
|
||||
className="text-5xl font-bold mb-4"
|
||||
style={{ color: section.heading.color }}
|
||||
>
|
||||
{section.heading.text}
|
||||
</h1>
|
||||
<h2
|
||||
className="text-2xl mb-8"
|
||||
style={{ color: section.subheading.color }}
|
||||
>
|
||||
{section.subheading.text}
|
||||
</h2>
|
||||
<div className="flex gap-4 justify-center">
|
||||
{section.buttons.map((button, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={button.link}
|
||||
className="px-6 py-3 rounded-lg font-medium"
|
||||
style={{
|
||||
backgroundColor: button.background,
|
||||
color: button.color
|
||||
}}
|
||||
>
|
||||
{button.text}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case 'text-and-image':
|
||||
return (
|
||||
<div
|
||||
key={`text-image-${section.title}`}
|
||||
className="container mx-auto py-16 px-4"
|
||||
>
|
||||
<div className={`flex items-center gap-12 ${section.flow === 'right' ? 'flex-row-reverse' : 'flex-row'}`}>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-3xl font-bold mb-4">{section.title}</h2>
|
||||
<p className="text-lg text-gray-600 mb-6">{section.text}</p>
|
||||
<div className="flex gap-4">
|
||||
{section.buttons.map((button, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={button.link}
|
||||
className="px-6 py-3 rounded-lg font-medium"
|
||||
style={{
|
||||
backgroundColor: button.background,
|
||||
color: button.color
|
||||
}}
|
||||
>
|
||||
{button.text}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<img
|
||||
src={section.image.url}
|
||||
alt={section.image.alt}
|
||||
className="rounded-lg shadow-lg w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case 'logos':
|
||||
return (
|
||||
<div
|
||||
key={`logos-${section.type}`}
|
||||
className="container mx-auto py-16 px-4"
|
||||
>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-8 items-center">
|
||||
{section.logos.map((logo, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={logo.url}
|
||||
alt={logo.alt}
|
||||
className="h-12 object-contain mx-auto"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case 'people':
|
||||
return (
|
||||
<div
|
||||
key={`people-${section.title}`}
|
||||
className="container mx-auto py-16 px-4"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-center mb-12">{section.title}</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{section.people.map((person, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<img
|
||||
src={person.image_url}
|
||||
alt={person.name}
|
||||
className="w-32 h-32 rounded-full mx-auto mb-4 object-cover"
|
||||
/>
|
||||
<h3 className="text-xl font-semibold mb-2">{person.name}</h3>
|
||||
<p className="text-gray-600">{person.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case 'featured-courses':
|
||||
return (
|
||||
<div
|
||||
key={`featured-courses-${section.title}`}
|
||||
className="container mx-auto py-16 px-4"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-center mb-12">{section.title}</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{section.courses.map((course, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-lg p-4">
|
||||
{/* Course card content - you'll need to fetch course details */}
|
||||
<p>Course ID: {course.course_uuid}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{landing.sections.map((section) => renderSection(section))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingCustom
|
||||
Loading…
Add table
Add a link
Reference in a new issue