mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init Hero, TextImage, Logos, People Landing sections
This commit is contained in:
parent
7b33639b9a
commit
1fdbb2b3ca
3 changed files with 158 additions and 65 deletions
|
|
@ -52,16 +52,56 @@ const PREDEFINED_GRADIENTS = {
|
||||||
colors: ['#f0fff4', '#dcfce7'] as Array<string>,
|
colors: ['#f0fff4', '#dcfce7'] as Array<string>,
|
||||||
direction: '45deg'
|
direction: '45deg'
|
||||||
},
|
},
|
||||||
'lavender-mist': {
|
'deep-ocean': {
|
||||||
colors: ['#faf5ff', '#f3e8ff'] as Array<string>,
|
colors: ['#0f172a', '#1e3a8a'] as Array<string>,
|
||||||
|
direction: '135deg'
|
||||||
|
},
|
||||||
|
'sunset-blaze': {
|
||||||
|
colors: ['#7f1d1d', '#ea580c'] as Array<string>,
|
||||||
direction: '45deg'
|
direction: '45deg'
|
||||||
},
|
},
|
||||||
'ocean-spray': {
|
'midnight-purple': {
|
||||||
colors: ['#f0f9ff', '#e0f2fe'] as Array<string>,
|
colors: ['#581c87', '#7e22ce'] as Array<string>,
|
||||||
|
direction: '90deg'
|
||||||
|
},
|
||||||
|
'forest-depths': {
|
||||||
|
colors: ['#064e3b', '#059669'] as Array<string>,
|
||||||
|
direction: '225deg'
|
||||||
|
},
|
||||||
|
'berry-fusion': {
|
||||||
|
colors: ['#831843', '#be185d'] as Array<string>,
|
||||||
|
direction: '135deg'
|
||||||
|
},
|
||||||
|
'cosmic-night': {
|
||||||
|
colors: ['#1e1b4b', '#4338ca'] as Array<string>,
|
||||||
direction: '45deg'
|
direction: '45deg'
|
||||||
},
|
},
|
||||||
'peach-cream': {
|
'autumn-fire': {
|
||||||
colors: ['#fff7ed', '#ffedd5'] as Array<string>,
|
colors: ['#7c2d12', '#c2410c'] as Array<string>,
|
||||||
|
direction: '90deg'
|
||||||
|
},
|
||||||
|
'emerald-depths': {
|
||||||
|
colors: ['#064e3b', '#10b981'] as Array<string>,
|
||||||
|
direction: '135deg'
|
||||||
|
},
|
||||||
|
'royal-navy': {
|
||||||
|
colors: ['#1e3a8a', '#3b82f6'] as Array<string>,
|
||||||
|
direction: '225deg'
|
||||||
|
},
|
||||||
|
'volcanic': {
|
||||||
|
colors: ['#991b1b', '#f97316'] as Array<string>,
|
||||||
|
direction: '315deg'
|
||||||
|
},
|
||||||
|
'arctic-night': {
|
||||||
|
colors: ['#0f172a', '#475569'] as Array<string>,
|
||||||
|
direction: '90deg'
|
||||||
|
},
|
||||||
|
'grape-punch': {
|
||||||
|
colors: ['#6b21a8', '#d946ef'] as Array<string>,
|
||||||
|
direction: '135deg'
|
||||||
|
},
|
||||||
|
'marine-blue': {
|
||||||
|
colors: ['#0c4a6e', '#0ea5e9'] as Array<string>,
|
||||||
direction: '45deg'
|
direction: '45deg'
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|
@ -150,6 +190,7 @@ const OrgEditLanding = () => {
|
||||||
case 'logos':
|
case 'logos':
|
||||||
return {
|
return {
|
||||||
type: 'logos',
|
type: 'logos',
|
||||||
|
title: 'New Logos Section',
|
||||||
logos: []
|
logos: []
|
||||||
}
|
}
|
||||||
case 'people':
|
case 'people':
|
||||||
|
|
@ -282,38 +323,62 @@ const OrgEditLanding = () => {
|
||||||
<div
|
<div
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
className={`p-4 bg-white/80 backdrop-blur-sm rounded-lg cursor-pointer nice-shadow ${
|
onClick={() => setSelectedSection(index)}
|
||||||
selectedSection === index ? 'border-blue-500 bg-blue-50/50' : 'border-gray-200 hover:border-gray-300'
|
className={`p-4 bg-white/80 backdrop-blur-sm rounded-lg cursor-pointer border ${
|
||||||
} ${snapshot.isDragging ? 'shadow-lg ring-2 ring-blue-500/20' : ''}`}
|
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' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between group">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div {...provided.dragHandleProps} className="text-gray-400 hover:text-gray-600">
|
<div {...provided.dragHandleProps}
|
||||||
|
className={`p-1.5 rounded-md transition-colors duration-200 ${
|
||||||
|
selectedSection === index
|
||||||
|
? 'text-blue-500 bg-blue-100/50'
|
||||||
|
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}>
|
||||||
<GripVertical size={16} />
|
<GripVertical size={16} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className={`p-1.5 rounded-md ${
|
||||||
|
selectedSection === index
|
||||||
|
? 'text-blue-600 bg-blue-100/50'
|
||||||
|
: 'text-gray-600 bg-gray-100/50'
|
||||||
|
}`}>
|
||||||
{React.createElement(SECTION_TYPES[section.type as keyof typeof SECTION_TYPES].icon, {
|
{React.createElement(SECTION_TYPES[section.type as keyof typeof SECTION_TYPES].icon, {
|
||||||
size: 16,
|
size: 16
|
||||||
className: "text-gray-600"
|
|
||||||
})}
|
})}
|
||||||
<span className="text-sm font-medium truncate capitalize">
|
</div>
|
||||||
|
<span className={`text-sm font-medium truncate capitalize ${
|
||||||
|
selectedSection === index
|
||||||
|
? 'text-blue-700'
|
||||||
|
: 'text-gray-700'
|
||||||
|
}`}>
|
||||||
{getSectionDisplayName(section)}
|
{getSectionDisplayName(section)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedSection(index)}
|
onClick={(e) => {
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md transition-colors duration-200"
|
e.stopPropagation()
|
||||||
|
setSelectedSection(index)
|
||||||
|
}}
|
||||||
|
className={`p-1.5 rounded-md transition-colors duration-200 ${
|
||||||
|
selectedSection === index
|
||||||
|
? 'text-blue-500 hover:bg-blue-100'
|
||||||
|
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Edit size={14} className="text-gray-600" />
|
<Edit size={14} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
deleteSection(index)
|
deleteSection(index)
|
||||||
}}
|
}}
|
||||||
className="p-1.5 hover:bg-red-50 rounded-md transition-colors duration-200"
|
className="p-1.5 text-red-400 hover:text-red-500 hover:bg-red-50 rounded-md transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} className="text-red-500" />
|
<Trash2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -986,6 +1051,17 @@ const LogosSectionEditor: React.FC<{
|
||||||
<div>
|
<div>
|
||||||
<Label>Logos</Label>
|
<Label>Logos</Label>
|
||||||
<div className="space-y-3 mt-2">
|
<div className="space-y-3 mt-2">
|
||||||
|
{/* Title */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="title">Title</Label>
|
||||||
|
<Input
|
||||||
|
id="title"
|
||||||
|
value={section.title}
|
||||||
|
onChange={(e) => onChange({ ...section, title: e.target.value })}
|
||||||
|
placeholder="Enter section title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{section.logos.map((logo, index) => (
|
{section.logos.map((logo, index) => (
|
||||||
<div key={index} className="grid grid-cols-[1fr,1fr,auto] gap-2">
|
<div key={index} className="grid grid-cols-[1fr,1fr,auto] gap-2">
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export interface LandingButton {
|
||||||
|
|
||||||
export interface LandingLogos {
|
export interface LandingLogos {
|
||||||
type: 'logos';
|
type: 'logos';
|
||||||
|
title: string;
|
||||||
logos: LandingImage[];
|
logos: LandingImage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`hero-${section.title}`}
|
key={`hero-${section.title}`}
|
||||||
className="min-h-[500px] flex items-center justify-center"
|
className="min-h-[400px] sm:min-h-[500px] mt-[20px] sm:mt-[40px] mx-2 sm:mx-4 lg:mx-16 w-full flex items-center justify-center rounded-xl border border-gray-100"
|
||||||
style={{
|
style={{
|
||||||
background: section.background.type === 'solid'
|
background: section.background.type === 'solid'
|
||||||
? section.background.color
|
? section.background.color
|
||||||
|
|
@ -25,25 +25,25 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
: `url(${section.background.image}) center/cover`
|
: `url(${section.background.image}) center/cover`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-center">
|
<div className="text-center w-full max-w-4xl mx-auto px-4 sm:px-6">
|
||||||
<h1
|
<h1
|
||||||
className="text-5xl font-bold mb-4"
|
className="text-xl sm:text-2xl md:text-3xl font-bold mb-2 sm:mb-4"
|
||||||
style={{ color: section.heading.color }}
|
style={{ color: section.heading.color }}
|
||||||
>
|
>
|
||||||
{section.heading.text}
|
{section.heading.text}
|
||||||
</h1>
|
</h1>
|
||||||
<h2
|
<h2
|
||||||
className="text-2xl mb-8"
|
className="text-sm sm:text-base md:text-lg mb-4 sm:mb-6 md:mb-8 font-medium px-4 max-w-2xl mx-auto"
|
||||||
style={{ color: section.subheading.color }}
|
style={{ color: section.subheading.color }}
|
||||||
>
|
>
|
||||||
{section.subheading.text}
|
{section.subheading.text}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex gap-4 justify-center">
|
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center items-center">
|
||||||
{section.buttons.map((button, index) => (
|
{section.buttons.map((button, index) => (
|
||||||
<a
|
<a
|
||||||
key={index}
|
key={index}
|
||||||
href={button.link}
|
href={button.link}
|
||||||
className="px-6 py-3 rounded-lg font-medium"
|
className="w-full sm:w-auto px-6 py-2.5 rounded-lg text-sm font-extrabold shadow transition-transform hover:scale-105"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: button.background,
|
backgroundColor: button.background,
|
||||||
color: button.color
|
color: button.color
|
||||||
|
|
@ -60,18 +60,24 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`text-image-${section.title}`}
|
key={`text-image-${section.title}`}
|
||||||
className="container mx-auto py-16 px-4"
|
className="mt-[20px] sm:mt-[40px] mx-2 sm:mx-4 lg:mx-16 w-full"
|
||||||
>
|
>
|
||||||
<div className={`flex items-center gap-12 ${section.flow === 'right' ? 'flex-row-reverse' : 'flex-row'}`}>
|
<div className={`flex flex-col md:flex-row items-center gap-8 md:gap-12 bg-white rounded-xl p-6 md:p-8 lg:p-12 nice-shadow ${
|
||||||
<div className="flex-1">
|
section.flow === 'right' ? 'md:flex-row-reverse' : ''
|
||||||
<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-1 w-full max-w-2xl">
|
||||||
<div className="flex gap-4">
|
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-gray-900 tracking-tight">{section.title}</h2>
|
||||||
|
<div className="prose prose-lg prose-gray max-w-none">
|
||||||
|
<p className="text-base md:text-lg leading-relaxed text-gray-600 whitespace-pre-line">
|
||||||
|
{section.text}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-4 mt-8">
|
||||||
{section.buttons.map((button, index) => (
|
{section.buttons.map((button, index) => (
|
||||||
<a
|
<a
|
||||||
key={index}
|
key={index}
|
||||||
href={button.link}
|
href={button.link}
|
||||||
className="px-6 py-3 rounded-lg font-medium"
|
className="px-6 py-3 rounded-xl font-medium shadow-sm transition-all duration-200 hover:scale-105"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: button.background,
|
backgroundColor: button.background,
|
||||||
color: button.color
|
color: button.color
|
||||||
|
|
@ -82,51 +88,61 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 w-full md:w-auto">
|
||||||
|
<div className="relative w-full aspect-[4/3] max-w-[500px] mx-auto">
|
||||||
<img
|
<img
|
||||||
src={section.image.url}
|
src={section.image.url}
|
||||||
alt={section.image.alt}
|
alt={section.image.alt}
|
||||||
className="rounded-lg shadow-lg w-full"
|
className="object-cover w-full h-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
case 'logos':
|
case 'logos':
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`logos-${section.type}`}
|
key={`logos-${section.type}`}
|
||||||
className="container mx-auto py-16 px-4"
|
className="mt-[20px] sm:mt-[40px] mx-2 sm:mx-4 lg:mx-16 w-full py-20"
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-8 items-center">
|
{section.title && (
|
||||||
|
<h2 className="text-2xl md:text-3xl font-bold text-left mb-16 text-gray-900">{section.title}</h2>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-center w-full">
|
||||||
|
<div className="flex flex-wrap justify-center gap-16 max-w-6xl">
|
||||||
{section.logos.map((logo, index) => (
|
{section.logos.map((logo, index) => (
|
||||||
|
<div key={index} className="flex items-center justify-center w-[140px] h-[80px]">
|
||||||
<img
|
<img
|
||||||
key={index}
|
|
||||||
src={logo.url}
|
src={logo.url}
|
||||||
alt={logo.alt}
|
alt={logo.alt}
|
||||||
className="h-12 object-contain mx-auto"
|
className="max-h-16 max-w-[120px] object-contain hover:opacity-80 transition-opacity"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
case 'people':
|
case 'people':
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`people-${section.title}`}
|
key={`people-${section.title}`}
|
||||||
className="container mx-auto py-16 px-4"
|
className="mt-[20px] sm:mt-[40px] mx-2 sm:mx-4 lg:mx-16 w-full py-16"
|
||||||
>
|
>
|
||||||
<h2 className="text-3xl font-bold text-center mb-12">{section.title}</h2>
|
<h2 className="text-2xl md:text-3xl font-bold text-left mb-10 text-gray-900">{section.title}</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="flex flex-wrap justify-center gap-x-20 gap-y-8">
|
||||||
{section.people.map((person, index) => (
|
{section.people.map((person, index) => (
|
||||||
<div key={index} className="text-center">
|
<div key={index} className="w-[140px] flex flex-col items-center">
|
||||||
|
<div className="w-24 h-24 mb-4">
|
||||||
<img
|
<img
|
||||||
src={person.image_url}
|
src={person.image_url}
|
||||||
alt={person.name}
|
alt={person.name}
|
||||||
className="w-32 h-32 rounded-full mx-auto mb-4 object-cover"
|
className="w-full h-full rounded-full object-cover border-4 border-white nice-shadow"
|
||||||
/>
|
/>
|
||||||
<h3 className="text-xl font-semibold mb-2">{person.name}</h3>
|
</div>
|
||||||
<p className="text-gray-600">{person.description}</p>
|
<h3 className="text-lg font-semibold text-center text-gray-900">{person.name}</h3>
|
||||||
|
<p className="text-sm text-center text-gray-600 mt-1">{person.description}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -136,9 +152,9 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`featured-courses-${section.title}`}
|
key={`featured-courses-${section.title}`}
|
||||||
className="container mx-auto py-16 px-4"
|
className="mt-[20px] sm:mt-[40px] mx-2 sm:mx-4 lg:mx-16 w-full py-16"
|
||||||
>
|
>
|
||||||
<h2 className="text-3xl font-bold text-center mb-12">{section.title}</h2>
|
<h2 className="text-2xl md:text-3xl font-bold text-left mb-8 text-gray-900">{section.title}</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{section.courses.map((course, index) => (
|
{section.courses.map((course, index) => (
|
||||||
<div key={index} className="bg-white rounded-lg shadow-lg p-4">
|
<div key={index} className="bg-white rounded-lg shadow-lg p-4">
|
||||||
|
|
@ -155,7 +171,7 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="flex flex-col items-center justify-between w-full max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-16 h-full">
|
||||||
{landing.sections.map((section) => renderSection(section))}
|
{landing.sections.map((section) => renderSection(section))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue