feat: init Hero, TextImage, Logos, People Landing sections

This commit is contained in:
swve 2025-03-02 12:19:28 +01:00
parent 7b33639b9a
commit 1fdbb2b3ca
3 changed files with 158 additions and 65 deletions

View file

@ -52,16 +52,56 @@ const PREDEFINED_GRADIENTS = {
colors: ['#f0fff4', '#dcfce7'] as Array<string>,
direction: '45deg'
},
'lavender-mist': {
colors: ['#faf5ff', '#f3e8ff'] as Array<string>,
'deep-ocean': {
colors: ['#0f172a', '#1e3a8a'] as Array<string>,
direction: '135deg'
},
'sunset-blaze': {
colors: ['#7f1d1d', '#ea580c'] as Array<string>,
direction: '45deg'
},
'ocean-spray': {
colors: ['#f0f9ff', '#e0f2fe'] as Array<string>,
'midnight-purple': {
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'
},
'peach-cream': {
colors: ['#fff7ed', '#ffedd5'] as Array<string>,
'autumn-fire': {
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'
}
} as const
@ -150,6 +190,7 @@ const OrgEditLanding = () => {
case 'logos':
return {
type: 'logos',
title: 'New Logos Section',
logos: []
}
case 'people':
@ -282,38 +323,62 @@ const OrgEditLanding = () => {
<div
ref={provided.innerRef}
{...provided.draggableProps}
className={`p-4 bg-white/80 backdrop-blur-sm rounded-lg cursor-pointer nice-shadow ${
selectedSection === index ? 'border-blue-500 bg-blue-50/50' : 'border-gray-200 hover:border-gray-300'
} ${snapshot.isDragging ? 'shadow-lg ring-2 ring-blue-500/20' : ''}`}
onClick={() => 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' : ''}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center justify-between group">
<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} />
</div>
{React.createElement(SECTION_TYPES[section.type as keyof typeof SECTION_TYPES].icon, {
size: 16,
className: "text-gray-600"
})}
<span className="text-sm font-medium truncate capitalize">
<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, {
size: 16
})}
</div>
<span className={`text-sm font-medium truncate capitalize ${
selectedSection === index
? 'text-blue-700'
: 'text-gray-700'
}`}>
{getSectionDisplayName(section)}
</span>
</div>
<div className="flex space-x-2">
<div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<button
onClick={() => setSelectedSection(index)}
className="p-1.5 hover:bg-gray-100 rounded-md transition-colors duration-200"
onClick={(e) => {
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
onClick={(e) => {
e.stopPropagation()
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>
</div>
</div>
@ -986,6 +1051,17 @@ const LogosSectionEditor: React.FC<{
<div>
<Label>Logos</Label>
<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) => (
<div key={index} className="grid grid-cols-[1fr,1fr,auto] gap-2">
<Input

View file

@ -31,6 +31,7 @@ export interface LandingButton {
export interface LandingLogos {
type: 'logos';
title: string;
logos: LandingImage[];
}

View file

@ -16,7 +16,7 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
return (
<div
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={{
background: section.background.type === 'solid'
? section.background.color
@ -25,25 +25,25 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
: `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
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 }}
>
{section.heading.text}
</h1>
<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 }}
>
{section.subheading.text}
</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) => (
<a
key={index}
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={{
backgroundColor: button.background,
color: button.color
@ -60,18 +60,24 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
return (
<div
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-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">
<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 ${
section.flow === 'right' ? 'md:flex-row-reverse' : ''
}`}>
<div className="flex-1 w-full max-w-2xl">
<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) => (
<a
key={index}
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={{
backgroundColor: button.background,
color: button.color
@ -82,12 +88,14 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
))}
</div>
</div>
<div className="flex-1">
<img
src={section.image.url}
alt={section.image.alt}
className="rounded-lg shadow-lg w-full"
/>
<div className="flex-1 w-full md:w-auto">
<div className="relative w-full aspect-[4/3] max-w-[500px] mx-auto">
<img
src={section.image.url}
alt={section.image.alt}
className="object-cover w-full h-full"
/>
</div>
</div>
</div>
</div>
@ -96,17 +104,23 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
return (
<div
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.logos.map((logo, index) => (
<img
key={index}
src={logo.url}
alt={logo.alt}
className="h-12 object-contain mx-auto"
/>
))}
{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) => (
<div key={index} className="flex items-center justify-center w-[140px] h-[80px]">
<img
src={logo.url}
alt={logo.alt}
className="max-h-16 max-w-[120px] object-contain hover:opacity-80 transition-opacity"
/>
</div>
))}
</div>
</div>
</div>
)
@ -114,19 +128,21 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
return (
<div
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>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<h2 className="text-2xl md:text-3xl font-bold text-left mb-10 text-gray-900">{section.title}</h2>
<div className="flex flex-wrap justify-center gap-x-20 gap-y-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 key={index} className="w-[140px] flex flex-col items-center">
<div className="w-24 h-24 mb-4">
<img
src={person.image_url}
alt={person.name}
className="w-full h-full rounded-full object-cover border-4 border-white nice-shadow"
/>
</div>
<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>
@ -136,9 +152,9 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
return (
<div
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">
{section.courses.map((course, index) => (
<div key={index} className="bg-white rounded-lg shadow-lg p-4">
@ -155,7 +171,7 @@ function LandingCustom({ landing, orgslug }: LandingCustomProps) {
}
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))}
</div>
)