2026-02-22 21:52:05 +05:30
'use client' ;
2026-02-24 03:16:56 +05:30
import { useEffect , useState , useCallback , useRef } from 'react' ;
2026-02-22 21:52:05 +05:30
import { useRouter } from 'next/navigation' ;
import {
cmsService ,
2026-02-24 03:16:56 +05:30
usersService ,
2026-02-24 03:30:35 +05:30
uploadService ,
2026-02-22 21:52:05 +05:30
getErrorMessage ,
CmsContentRecord ,
HeroContent ,
FeaturesContent ,
FeatureItem ,
TopProfessionalsContent ,
ProfessionalItem ,
TestimonialsContent ,
TestimonialItem ,
StatItem ,
2026-02-24 03:30:35 +05:30
AboutHeroContent ,
AboutStatsContent ,
AboutStatItem ,
AboutFeaturesContent ,
AboutFeatureItem ,
AboutTeamContent ,
AboutTeamMember ,
AboutCtaContent ,
2026-02-24 03:16:56 +05:30
User ,
2026-02-22 21:52:05 +05:30
} from '@/services' ;
// ---------------------------------------------------------------------------
// Section metadata
// ---------------------------------------------------------------------------
interface SectionMeta {
2026-02-24 03:30:35 +05:30
pageSlug : string ;
2026-02-22 21:52:05 +05:30
sectionKey : string ;
label : string ;
description : string ;
}
2026-02-24 03:30:35 +05:30
const LANDING_SECTIONS : SectionMeta [ ] = [
{ pageSlug : 'landing' , sectionKey : 'hero' , label : 'Hero' , description : 'Main headline, description and call-to-action' } ,
{ pageSlug : 'landing' , sectionKey : 'features' , label : 'Features' , description : 'Feature highlights with icons' } ,
{ pageSlug : 'landing' , sectionKey : 'topProfessionals' , label : 'Top Professionals' , description : 'Agents and lenders showcase' } ,
{ pageSlug : 'landing' , sectionKey : 'testimonials' , label : 'Testimonials' , description : 'Reviews, stats and social proof' } ,
] ;
const ABOUT_SECTIONS : SectionMeta [ ] = [
{ pageSlug : 'about' , sectionKey : 'hero' , label : 'Hero & Banner' , description : 'Headline, description, CTA and banner image' } ,
{ pageSlug : 'about' , sectionKey : 'stats' , label : 'Stats' , description : 'Key metrics like verified agents count' } ,
{ pageSlug : 'about' , sectionKey : 'features' , label : 'Why Choose Us' , description : 'Feature cards with icons' } ,
{ pageSlug : 'about' , sectionKey : 'team' , label : 'Team' , description : 'Team members with photos' } ,
{ pageSlug : 'about' , sectionKey : 'cta' , label : 'Call to Action' , description : 'Bottom CTA section' } ,
2026-02-22 21:52:05 +05:30
] ;
// ---------------------------------------------------------------------------
// Default content factories
// ---------------------------------------------------------------------------
function defaultHero ( ) : HeroContent {
return { headline : '' , description : '' , ctaButtonText : '' , helperText : '' } ;
}
function defaultFeatures ( ) : FeaturesContent {
return { title : '' , subtitle : '' , features : [ ] } ;
}
function defaultTopProfessionals ( ) : TopProfessionalsContent {
return { title : '' , ctaText : '' , ctaButtonText : '' , agents : [ ] , lenders : [ ] } ;
}
function defaultTestimonials ( ) : TestimonialsContent {
return { title : '' , subtitle : '' , ratingInfo : '' , stats : [ ] , testimonials : [ ] } ;
}
2026-02-24 03:30:35 +05:30
function defaultContentFor ( pageSlug : string , sectionKey : string ) : unknown {
if ( pageSlug === 'about' ) {
switch ( sectionKey ) {
case 'hero' : return { badge : '' , headline : '' , description : '' , ctaButtonText : '' , bannerImageUrl : '' , bannerOverlayText : '' } as AboutHeroContent ;
case 'stats' : return { stats : [ ] } as AboutStatsContent ;
case 'features' : return { badge : '' , features : [ ] } as AboutFeaturesContent ;
case 'team' : return { title : '' , subtitle : '' , members : [ ] } as AboutTeamContent ;
case 'cta' : return { title : '' , description : '' , buttonText : '' } as AboutCtaContent ;
}
}
2026-02-22 21:52:05 +05:30
switch ( sectionKey ) {
case 'hero' : return defaultHero ( ) ;
case 'features' : return defaultFeatures ( ) ;
case 'topProfessionals' : return defaultTopProfessionals ( ) ;
case 'testimonials' : return defaultTestimonials ( ) ;
default : return { } ;
}
}
function emptyFeatureItem ( ) : FeatureItem {
return { iconPath : '' , title : '' , description : '' } ;
}
function emptyProfessionalItem ( ) : ProfessionalItem {
return { name : '' , subtitle : '' , location : '' , experience : '' , expertise : [ ] , imageUrl : '' } ;
}
function emptyTestimonialItem ( ) : TestimonialItem {
return { rating : 5 , title : '' , content : '' , author : '' , role : '' } ;
}
function emptyStatItem ( ) : StatItem {
return { iconPath : '' , boldText : '' , normalText : '' } ;
}
// ---------------------------------------------------------------------------
// Shared input class
// ---------------------------------------------------------------------------
const inputCls = 'w-full px-3 py-2 border border-[#e5e7eb] rounded-xl focus:outline-none focus:border-[#f5a623] text-[#00293d] bg-white placeholder:text-[#666666]' ;
const textareaCls = inputCls ;
// ---------------------------------------------------------------------------
// Page component
// ---------------------------------------------------------------------------
export default function CmsPage() {
const router = useRouter ( ) ;
2026-02-24 03:30:35 +05:30
const [ activePage , setActivePage ] = useState < 'landing' | 'about' > ( 'landing' ) ;
2026-02-22 21:52:05 +05:30
const [ records , setRecords ] = useState < CmsContentRecord [ ] > ( [ ] ) ;
2026-02-24 03:30:35 +05:30
const [ aboutRecords , setAboutRecords ] = useState < CmsContentRecord [ ] > ( [ ] ) ;
2026-02-22 21:52:05 +05:30
const [ isLoading , setIsLoading ] = useState ( true ) ;
const [ error , setError ] = useState ( '' ) ;
const [ notification , setNotification ] = useState < { type : 'success' | 'error' ; message : string } | null > ( null ) ;
// Modal
2026-02-24 03:30:35 +05:30
const [ editingSection , setEditingSection ] = useState < SectionMeta | null > ( null ) ;
2026-02-22 21:52:05 +05:30
const [ editContent , setEditContent ] = useState < unknown > ( null ) ;
const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
const [ modalError , setModalError ] = useState ( '' ) ;
const [ professionalsTab , setProfessionalsTab ] = useState < 'agents' | 'lenders' > ( 'agents' ) ;
2026-02-24 03:30:35 +05:30
const [ isUploadingImage , setIsUploadingImage ] = useState ( false ) ;
2026-02-22 21:52:05 +05:30
2026-02-24 03:16:56 +05:30
// Agent search
const [ agentSearchQuery , setAgentSearchQuery ] = useState ( '' ) ;
const [ agentSearchResults , setAgentSearchResults ] = useState < User [ ] > ( [ ] ) ;
const [ isSearchingAgents , setIsSearchingAgents ] = useState ( false ) ;
const [ showAgentDropdown , setShowAgentDropdown ] = useState ( false ) ;
const agentSearchTimeout = useRef < NodeJS.Timeout | null > ( null ) ;
const agentSearchRef = useRef < HTMLDivElement > ( null ) ;
2026-02-24 03:30:35 +05:30
// Fetch all CMS records
2026-02-22 21:52:05 +05:30
const fetchRecords = useCallback ( async ( ) = > {
setIsLoading ( true ) ;
setError ( '' ) ;
try {
2026-02-24 03:30:35 +05:30
const [ landingData , aboutData ] = await Promise . all ( [
cmsService . getByPage ( 'landing' ) ,
cmsService . getByPage ( 'about' ) ,
] ) ;
setRecords ( landingData ) ;
setAboutRecords ( aboutData ) ;
2026-02-22 21:52:05 +05:30
} catch ( err ) {
const msg = getErrorMessage ( err ) ;
setError ( msg ) ;
if ( msg . includes ( 'Unauthorized' ) ) {
router . push ( '/login' ) ;
}
} finally {
setIsLoading ( false ) ;
}
} , [ router ] ) ;
useEffect ( ( ) = > {
fetchRecords ( ) ;
} , [ fetchRecords ] ) ;
// Auto-dismiss notifications after 4 seconds
useEffect ( ( ) = > {
if ( notification ) {
const timer = setTimeout ( ( ) = > setNotification ( null ) , 4000 ) ;
return ( ) = > clearTimeout ( timer ) ;
}
} , [ notification ] ) ;
2026-02-24 03:16:56 +05:30
// Close agent search dropdown on outside click
useEffect ( ( ) = > {
function handleClickOutside ( e : MouseEvent ) {
if ( agentSearchRef . current && ! agentSearchRef . current . contains ( e . target as Node ) ) {
setShowAgentDropdown ( false ) ;
}
}
document . addEventListener ( 'mousedown' , handleClickOutside ) ;
return ( ) = > document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
} , [ ] ) ;
// Agent search with debounce
function handleAgentSearch ( query : string ) {
setAgentSearchQuery ( query ) ;
if ( agentSearchTimeout . current ) clearTimeout ( agentSearchTimeout . current ) ;
if ( query . length < 2 ) {
setAgentSearchResults ( [ ] ) ;
setShowAgentDropdown ( false ) ;
return ;
}
setIsSearchingAgents ( true ) ;
setShowAgentDropdown ( true ) ;
agentSearchTimeout . current = setTimeout ( async ( ) = > {
try {
const res = await usersService . getUsers ( { role : 'AGENT' , search : query , limit : 10 } ) ;
setAgentSearchResults ( res . users ) ;
} catch {
setAgentSearchResults ( [ ] ) ;
} finally {
setIsSearchingAgents ( false ) ;
}
} , 300 ) ;
}
function selectAgent ( user : User , onChange : ( items : ProfessionalItem [ ] ) = > void , currentItems : ProfessionalItem [ ] ) {
if ( currentItems . length >= 3 ) return ;
const profile = user . profile ;
const agent = user . agentProfile ;
const name = profile ? ` ${ profile . firstName } ${ profile . lastName } ` . trim ( ) : '' ;
const subtitle = agent ? . headline || agent ? . agentType ? . name || '' ;
const location = profile ? [ profile . city , profile . state ] . filter ( Boolean ) . join ( ', ' ) : '' ;
const experience = agent ? . yearsOfExperience ? ` ${ agent . yearsOfExperience } + years in real estate. ` : '' ;
const imageUrl = profile ? . avatar || '' ;
const newItem : ProfessionalItem = { name , subtitle , location , experience , expertise : [ ] , imageUrl } ;
onChange ( [ . . . currentItems , newItem ] ) ;
setAgentSearchQuery ( '' ) ;
setAgentSearchResults ( [ ] ) ;
setShowAgentDropdown ( false ) ;
}
2026-02-22 21:52:05 +05:30
// Helpers
2026-02-24 03:30:35 +05:30
function recordFor ( pageSlug : string , sectionKey : string ) : CmsContentRecord | undefined {
const source = pageSlug === 'about' ? aboutRecords : records ;
return source . find ( ( r ) = > r . sectionKey === sectionKey ) ;
2026-02-22 21:52:05 +05:30
}
// Open modal
2026-02-24 03:30:35 +05:30
function openEditor ( section : SectionMeta ) {
const existing = recordFor ( section . pageSlug , section . sectionKey ) ;
setEditContent ( existing ? JSON . parse ( JSON . stringify ( existing . content ) ) : defaultContentFor ( section . pageSlug , section . sectionKey ) ) ;
2026-02-22 21:52:05 +05:30
setModalError ( '' ) ;
setProfessionalsTab ( 'agents' ) ;
2026-02-24 03:16:56 +05:30
setAgentSearchQuery ( '' ) ;
setAgentSearchResults ( [ ] ) ;
setShowAgentDropdown ( false ) ;
2026-02-24 03:30:35 +05:30
setEditingSection ( section ) ;
2026-02-22 21:52:05 +05:30
}
function closeEditor() {
setEditingSection ( null ) ;
setEditContent ( null ) ;
setModalError ( '' ) ;
}
// Save
async function handleSave() {
if ( ! editingSection || editContent == null ) return ;
setIsSubmitting ( true ) ;
setModalError ( '' ) ;
try {
await cmsService . upsert ( {
2026-02-24 03:30:35 +05:30
pageSlug : editingSection.pageSlug ,
sectionKey : editingSection.sectionKey ,
2026-02-22 21:52:05 +05:30
content : editContent ,
isPublished : true ,
} ) ;
closeEditor ( ) ;
fetchRecords ( ) ;
setNotification ( { type : 'success' , message : 'Section saved successfully.' } ) ;
} catch ( err ) {
setModalError ( getErrorMessage ( err ) ) ;
} finally {
setIsSubmitting ( false ) ;
}
}
2026-02-24 03:30:35 +05:30
// Generic image upload — returns the presigned download URL
async function handleImageUpload ( file : File ) : Promise < string > {
setIsUploadingImage ( true ) ;
setModalError ( '' ) ;
try {
const { uploadUrl , key } = await uploadService . getPresignedUploadUrl ( file . name , file . type , 'properties' ) ;
await uploadService . uploadFileToS3 ( uploadUrl , file ) ;
const downloadUrl = await uploadService . getPresignedDownloadUrl ( key ) ;
return downloadUrl ;
} catch ( err ) {
setModalError ( getErrorMessage ( err ) ) ;
return '' ;
} finally {
setIsUploadingImage ( false ) ;
}
}
2026-02-22 21:52:05 +05:30
// ---------------------------------------------------------------------------
// Section-specific form renderers
// ---------------------------------------------------------------------------
function renderHeroForm() {
const data = editContent as HeroContent ;
const update = ( patch : Partial < HeroContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Headline < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . headline } onChange = { ( e ) = > update ( { headline : e.target.value } ) } placeholder = "Main headline text" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Description < / label >
< textarea rows = { 3 } className = { textareaCls } value = { data . description } onChange = { ( e ) = > update ( { description : e.target.value } ) } placeholder = "Sub-heading description" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > CTA Button Text < / label >
< input type = "text" className = { inputCls } value = { data . ctaButtonText } onChange = { ( e ) = > update ( { ctaButtonText : e.target.value } ) } placeholder = "e.g. Get Started" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Helper Text < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . helperText } onChange = { ( e ) = > update ( { helperText : e.target.value } ) } placeholder = "Small text below CTA" / >
< / div >
< / div >
) ;
}
function renderFeaturesForm() {
const data = editContent as FeaturesContent ;
const update = ( patch : Partial < FeaturesContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
function updateFeature ( index : number , patch : Partial < FeatureItem > ) {
const features = [ . . . data . features ] ;
features [ index ] = { . . . features [ index ] , . . . patch } ;
update ( { features } ) ;
}
function removeFeature ( index : number ) {
update ( { features : data.features.filter ( ( _ , i ) = > i !== index ) } ) ;
}
function addFeature() {
update ( { features : [ . . . data . features , emptyFeatureItem ( ) ] } ) ;
}
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Title < / label >
< input type = "text" className = { inputCls } value = { data . title } onChange = { ( e ) = > update ( { title : e.target.value } ) } placeholder = "Section title" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Subtitle < / label >
< input type = "text" className = { inputCls } value = { data . subtitle } onChange = { ( e ) = > update ( { subtitle : e.target.value } ) } placeholder = "Section subtitle" / >
< / div >
< div >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Features ( { data . features . length } ) < / label >
< button type = "button" onClick = { addFeature } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Feature < / button >
< / div >
{ data . features . length === 0 && < p className = "text-sm text-[#666666] italic" > No features added yet . < / p > }
< div className = "space-y-3" >
{ data . features . map ( ( feat , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Feature { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > removeFeature ( idx ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< input type = "text" className = { inputCls } value = { feat . iconPath } onChange = { ( e ) = > updateFeature ( idx , { iconPath : e.target.value } ) } placeholder = "Icon path (e.g. /assets/icons/icon.svg)" / >
< input type = "text" className = { inputCls } value = { feat . title } onChange = { ( e ) = > updateFeature ( idx , { title : e.target.value } ) } placeholder = "Feature title" / >
< textarea rows = { 2 } className = { textareaCls } value = { feat . description } onChange = { ( e ) = > updateFeature ( idx , { description : e.target.value } ) } placeholder = "Feature description" / >
< / div >
) ) }
< / div >
< / div >
< / div >
) ;
}
function renderProfessionalsList (
items : ProfessionalItem [ ] ,
label : string ,
onChange : ( items : ProfessionalItem [ ] ) = > void ,
) {
function updateItem ( index : number , patch : Partial < ProfessionalItem > ) {
const updated = [ . . . items ] ;
updated [ index ] = { . . . updated [ index ] , . . . patch } ;
onChange ( updated ) ;
}
function removeItem ( index : number ) {
onChange ( items . filter ( ( _ , i ) = > i !== index ) ) ;
}
function addItem() {
2026-02-24 03:16:56 +05:30
if ( items . length >= 3 ) return ;
2026-02-22 21:52:05 +05:30
onChange ( [ . . . items , emptyProfessionalItem ( ) ] ) ;
}
2026-02-24 03:16:56 +05:30
const atMax = items . length >= 3 ;
2026-02-22 21:52:05 +05:30
return (
< div >
2026-02-24 03:16:56 +05:30
{ /* Agent search */ }
{ ! atMax && < div ref = { agentSearchRef } className = "relative mb-4" >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Search agents from database < / label >
< div className = "relative" >
< input
type = "text"
className = { inputCls }
value = { agentSearchQuery }
onChange = { ( e ) = > handleAgentSearch ( e . target . value ) }
onFocus = { ( ) = > { if ( agentSearchResults . length > 0 ) setShowAgentDropdown ( true ) ; } }
placeholder = "Search agents by name or email..."
/ >
{ isSearchingAgents && (
< div className = "absolute right-3 top-1/2 -translate-y-1/2" >
< div className = "animate-spin rounded-full h-4 w-4 border-b-2 border-[#f5a623]" > < / div >
< / div >
) }
< / div >
{ showAgentDropdown && agentSearchQuery . length >= 2 && (
< div className = "absolute z-10 w-full mt-1 bg-white border border-[#e5e7eb] rounded-lg shadow-lg max-h-60 overflow-y-auto" >
{ isSearchingAgents && agentSearchResults . length === 0 ? (
< div className = "px-4 py-3 text-sm text-[#666666]" > Searching . . . < / div >
) : agentSearchResults . length === 0 ? (
< div className = "px-4 py-3 text-sm text-[#666666]" > No agents found < / div >
) : (
agentSearchResults . map ( ( user ) = > (
< button
key = { user . id }
type = "button"
onClick = { ( ) = > selectAgent ( user , onChange , items ) }
className = "w-full text-left px-4 py-2.5 hover:bg-[#f5f9f8] transition-colors border-b border-[#e5e7eb] last:border-b-0"
>
< div className = "flex items-center gap-3" >
< div className = "w-8 h-8 rounded-full bg-[#f5a623]/10 flex items-center justify-center flex-shrink-0 relative overflow-hidden" >
< span className = "text-xs font-semibold text-[#f5a623]" >
{ user . profile ? . firstName ? . [ 0 ] || '' } { user . profile ? . lastName ? . [ 0 ] || '' }
< / span >
{ user . profile ? . avatar && (
< img src = { user . profile . avatar } alt = "" className = "absolute inset-0 w-full h-full rounded-full object-cover" onError = { ( e ) = > { ( e . target as HTMLImageElement ) . style . display = 'none' ; } } / >
) }
< / div >
< div className = "min-w-0" >
< div className = "text-sm font-medium text-[#00293d] truncate" >
{ user . profile ? ` ${ user . profile . firstName } ${ user . profile . lastName } ` : 'No Name' }
< span className = "ml-2 text-xs text-[#666666] font-normal" > { user . email } < / span >
< / div >
< div className = "text-xs text-[#666666] truncate" >
{ [ user . profile ? . city , user . profile ? . state ] . filter ( Boolean ) . join ( ', ' ) || 'No location' }
{ user . agentProfile ? . agentType ? . name && (
< span className = "ml-2 px-1.5 py-0.5 bg-[#f5a623]/10 text-[#f5a623] rounded text-[10px] font-medium" >
{ user . agentProfile . agentType . name }
< / span >
) }
< / div >
< / div >
< / div >
< / button >
) )
) }
< / div >
) }
< / div > }
2026-02-22 21:52:05 +05:30
< div className = "flex items-center justify-between mb-2" >
2026-02-24 03:16:56 +05:30
< label className = "block text-sm font-medium text-[#00293d]" > { label } ( { items . length } / 3 ) < / label >
< button type = "button" onClick = { addItem } disabled = { atMax } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > + Add { label . slice ( 0 , - 1 ) } < / button >
2026-02-22 21:52:05 +05:30
< / div >
2026-02-24 03:16:56 +05:30
{ atMax && < p className = "text-xs text-[#666666] mb-2" > Maximum of 3 { label . toLowerCase ( ) } reached . < / p > }
2026-02-22 21:52:05 +05:30
{ items . length === 0 && < p className = "text-sm text-[#666666] italic" > No { label . toLowerCase ( ) } added yet . < / p > }
< div className = "space-y-3" >
{ items . map ( ( item , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > { label . slice ( 0 , - 1 ) } { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > removeItem ( idx ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< div className = "grid grid-cols-2 gap-2" >
< input type = "text" className = { inputCls } value = { item . name } onChange = { ( e ) = > updateItem ( idx , { name : e.target.value } ) } placeholder = "Name" / >
< input type = "text" className = { inputCls } value = { item . subtitle } onChange = { ( e ) = > updateItem ( idx , { subtitle : e.target.value } ) } placeholder = "Subtitle" / >
< input type = "text" className = { inputCls } value = { item . location } onChange = { ( e ) = > updateItem ( idx , { location : e.target.value } ) } placeholder = "Location" / >
< input type = "text" className = { inputCls } value = { item . experience } onChange = { ( e ) = > updateItem ( idx , { experience : e.target.value } ) } placeholder = "Experience" / >
< / div >
< input type = "text" className = { inputCls } value = { item . expertise . join ( ', ' ) } onChange = { ( e ) = > updateItem ( idx , { expertise : e.target.value.split ( ',' ) . map ( ( s ) = > s . trim ( ) ) . filter ( Boolean ) } ) } placeholder = "Expertise (comma-separated)" / >
< input type = "text" className = { inputCls } value = { item . imageUrl } onChange = { ( e ) = > updateItem ( idx , { imageUrl : e.target.value } ) } placeholder = "Image URL" / >
< / div >
) ) }
< / div >
< / div >
) ;
}
function renderTopProfessionalsForm() {
const data = editContent as TopProfessionalsContent ;
const update = ( patch : Partial < TopProfessionalsContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Title < / label >
< input type = "text" className = { inputCls } value = { data . title } onChange = { ( e ) = > update ( { title : e.target.value } ) } placeholder = "Section title" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > CTA Text < / label >
< input type = "text" className = { inputCls } value = { data . ctaText } onChange = { ( e ) = > update ( { ctaText : e.target.value } ) } placeholder = "Call-to-action text" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > CTA Button Text < / label >
< input type = "text" className = { inputCls } value = { data . ctaButtonText } onChange = { ( e ) = > update ( { ctaButtonText : e.target.value } ) } placeholder = "Button label" / >
< / div >
{ /* Tabs */ }
< div className = "border-b border-[#e5e7eb]" >
< nav className = "flex -mb-px" >
< button
type = "button"
onClick = { ( ) = > setProfessionalsTab ( 'agents' ) }
className = { ` px-4 py-2 text-sm font-medium border-b-2 transition-colors ${ professionalsTab === 'agents' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]' } ` }
>
Agents ( { data . agents . length } )
< / button >
< button
type = "button"
onClick = { ( ) = > setProfessionalsTab ( 'lenders' ) }
className = { ` px-4 py-2 text-sm font-medium border-b-2 transition-colors ${ professionalsTab === 'lenders' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]' } ` }
>
Lenders ( { data . lenders . length } )
< / button >
< / nav >
< / div >
{ professionalsTab === 'agents' && renderProfessionalsList ( data . agents , 'Agents' , ( agents ) = > update ( { agents } ) ) }
{ professionalsTab === 'lenders' && renderProfessionalsList ( data . lenders , 'Lenders' , ( lenders ) = > update ( { lenders } ) ) }
< / div >
) ;
}
function renderTestimonialsForm() {
const data = editContent as TestimonialsContent ;
const update = ( patch : Partial < TestimonialsContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
function updateStat ( index : number , patch : Partial < StatItem > ) {
const stats = [ . . . data . stats ] ;
stats [ index ] = { . . . stats [ index ] , . . . patch } ;
update ( { stats } ) ;
}
function removeStat ( index : number ) {
update ( { stats : data.stats.filter ( ( _ , i ) = > i !== index ) } ) ;
}
function addStat() {
update ( { stats : [ . . . data . stats , emptyStatItem ( ) ] } ) ;
}
function updateTestimonial ( index : number , patch : Partial < TestimonialItem > ) {
const testimonials = [ . . . data . testimonials ] ;
testimonials [ index ] = { . . . testimonials [ index ] , . . . patch } ;
update ( { testimonials } ) ;
}
function removeTestimonial ( index : number ) {
update ( { testimonials : data.testimonials.filter ( ( _ , i ) = > i !== index ) } ) ;
}
function addTestimonial() {
update ( { testimonials : [ . . . data . testimonials , emptyTestimonialItem ( ) ] } ) ;
}
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Title < / label >
< input type = "text" className = { inputCls } value = { data . title } onChange = { ( e ) = > update ( { title : e.target.value } ) } placeholder = "Section title" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Subtitle < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . subtitle } onChange = { ( e ) = > update ( { subtitle : e.target.value } ) } placeholder = "Section subtitle" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Rating Info < / label >
< input type = "text" className = { inputCls } value = { data . ratingInfo } onChange = { ( e ) = > update ( { ratingInfo : e.target.value } ) } placeholder = "e.g. 4.9/5 based on 200+ reviews" / >
< / div >
{ /* Stats */ }
< div >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Stats ( { data . stats . length } ) < / label >
< button type = "button" onClick = { addStat } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Stat < / button >
< / div >
{ data . stats . length === 0 && < p className = "text-sm text-[#666666] italic" > No stats added yet . < / p > }
< div className = "space-y-3" >
{ data . stats . map ( ( stat , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Stat { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > removeStat ( idx ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< input type = "text" className = { inputCls } value = { stat . iconPath } onChange = { ( e ) = > updateStat ( idx , { iconPath : e.target.value } ) } placeholder = "Icon path" / >
< div className = "grid grid-cols-2 gap-2" >
< input type = "text" className = { inputCls } value = { stat . boldText } onChange = { ( e ) = > updateStat ( idx , { boldText : e.target.value } ) } placeholder = "Bold text (e.g. 500+)" / >
< input type = "text" className = { inputCls } value = { stat . normalText } onChange = { ( e ) = > updateStat ( idx , { normalText : e.target.value } ) } placeholder = "Normal text (e.g. Properties)" / >
< / div >
< / div >
) ) }
< / div >
< / div >
{ /* Testimonials */ }
< div >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Testimonials ( { data . testimonials . length } ) < / label >
< button type = "button" onClick = { addTestimonial } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Testimonial < / button >
< / div >
{ data . testimonials . length === 0 && < p className = "text-sm text-[#666666] italic" > No testimonials added yet . < / p > }
< div className = "space-y-3" >
{ data . testimonials . map ( ( item , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Testimonial { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > removeTestimonial ( idx ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< div className = "grid grid-cols-2 gap-2" >
< div >
< label className = "block text-xs text-[#666666] mb-0.5" > Rating < / label >
< input type = "number" min = { 1 } max = { 5 } step = { 0.1 } className = { inputCls } value = { item . rating } onChange = { ( e ) = > updateTestimonial ( idx , { rating : parseFloat ( e . target . value ) || 0 } ) } / >
< / div >
< div >
< label className = "block text-xs text-[#666666] mb-0.5" > Title < / label >
< input type = "text" className = { inputCls } value = { item . title } onChange = { ( e ) = > updateTestimonial ( idx , { title : e.target.value } ) } placeholder = "Review title" / >
< / div >
< / div >
< textarea rows = { 2 } className = { textareaCls } value = { item . content } onChange = { ( e ) = > updateTestimonial ( idx , { content : e.target.value } ) } placeholder = "Testimonial content" / >
< div className = "grid grid-cols-2 gap-2" >
< input type = "text" className = { inputCls } value = { item . author } onChange = { ( e ) = > updateTestimonial ( idx , { author : e.target.value } ) } placeholder = "Author name" / >
< input type = "text" className = { inputCls } value = { item . role } onChange = { ( e ) = > updateTestimonial ( idx , { role : e.target.value } ) } placeholder = "Author role" / >
< / div >
< / div >
) ) }
< / div >
< / div >
< / div >
) ;
}
2026-02-24 03:30:35 +05:30
// Reusable image upload field
function renderImageUploadField ( label : string , currentUrl : string , onUrlChange : ( url : string ) = > void ) {
return (
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > { label } < / label >
{ currentUrl && (
< div className = "mb-3 relative rounded-lg overflow-hidden border border-[#e5e7eb]" >
< img src = { currentUrl } alt = "Preview" className = "w-full h-48 object-cover" onError = { ( e ) = > { ( e . target as HTMLImageElement ) . style . display = 'none' ; } } / >
< button type = "button" onClick = { ( ) = > onUrlChange ( '' ) } className = "absolute top-2 right-2 px-2 py-1 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors" > Remove < / button >
< / div >
) }
< div className = "flex gap-2 mb-2" >
< label className = { ` flex-shrink-0 px-4 py-2 text-sm font-medium rounded-lg transition-colors cursor-pointer ${ isUploadingImage ? 'bg-gray-300 text-gray-500' : 'bg-[#f5a623] hover:bg-[#e09620] text-white' } ` } >
{ isUploadingImage ? 'Uploading...' : 'Upload Image' }
< input type = "file" accept = "image/*" className = "hidden" disabled = { isUploadingImage } onChange = { async ( e ) = > {
const file = e . target . files ? . [ 0 ] ;
if ( file ) { const url = await handleImageUpload ( file ) ; if ( url ) onUrlChange ( url ) ; }
e . target . value = '' ;
} } / >
< / label >
< span className = "text-xs text-[#666666] self-center" > or enter URL below < / span >
< / div >
< input type = "text" className = { inputCls } value = { currentUrl } onChange = { ( e ) = > onUrlChange ( e . target . value ) } placeholder = "https://..." / >
< / div >
) ;
}
// --- About Us form renderers ---
function renderAboutHeroForm() {
const data = editContent as AboutHeroContent ;
const update = ( patch : Partial < AboutHeroContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Badge Text < / label >
< input type = "text" className = { inputCls } value = { data . badge } onChange = { ( e ) = > update ( { badge : e.target.value } ) } placeholder = "e.g. Our Mission" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Headline < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . headline } onChange = { ( e ) = > update ( { headline : e.target.value } ) } placeholder = "Main headline" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Description < / label >
< textarea rows = { 3 } className = { textareaCls } value = { data . description } onChange = { ( e ) = > update ( { description : e.target.value } ) } placeholder = "Sub-heading description" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > CTA Button Text < / label >
< input type = "text" className = { inputCls } value = { data . ctaButtonText } onChange = { ( e ) = > update ( { ctaButtonText : e.target.value } ) } placeholder = "e.g. Find Your Agent" / >
< / div >
{ renderImageUploadField ( 'Banner Image' , data . bannerImageUrl , ( url ) = > update ( { bannerImageUrl : url } ) ) }
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Banner Overlay Text < / label >
< input type = "text" className = { inputCls } value = { data . bannerOverlayText } onChange = { ( e ) = > update ( { bannerOverlayText : e.target.value } ) } placeholder = "e.g. Building the future of property." / >
< / div >
< / div >
) ;
}
function renderAboutStatsForm() {
const data = editContent as AboutStatsContent ;
const update = ( patch : Partial < AboutStatsContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
function updateStat ( index : number , patch : Partial < AboutStatItem > ) {
const stats = [ . . . data . stats ] ;
stats [ index ] = { . . . stats [ index ] , . . . patch } ;
update ( { stats } ) ;
}
return (
< div className = "space-y-4" >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Stats ( { data . stats . length } ) < / label >
< button type = "button" onClick = { ( ) = > update ( { stats : [ . . . data . stats , { value : '' , label : '' } ] } ) } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Stat < / button >
< / div >
{ data . stats . length === 0 && < p className = "text-sm text-[#666666] italic" > No stats added yet . < / p > }
< div className = "space-y-3" >
{ data . stats . map ( ( stat , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Stat { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > update ( { stats : data.stats.filter ( ( _ , i ) = > i !== idx ) } ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< div className = "grid grid-cols-2 gap-2" >
< input type = "text" className = { inputCls } value = { stat . value } onChange = { ( e ) = > updateStat ( idx , { value : e.target.value } ) } placeholder = "Value (e.g. 15k+)" / >
< input type = "text" className = { inputCls } value = { stat . label } onChange = { ( e ) = > updateStat ( idx , { label : e.target.value } ) } placeholder = "Label (e.g. Verified Agents)" / >
< / div >
< / div >
) ) }
< / div >
< / div >
) ;
}
function renderAboutFeaturesForm() {
const data = editContent as AboutFeaturesContent ;
const update = ( patch : Partial < AboutFeaturesContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
function updateFeature ( index : number , patch : Partial < AboutFeatureItem > ) {
const features = [ . . . data . features ] ;
features [ index ] = { . . . features [ index ] , . . . patch } ;
update ( { features } ) ;
}
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Section Badge < / label >
< input type = "text" className = { inputCls } value = { data . badge } onChange = { ( e ) = > update ( { badge : e.target.value } ) } placeholder = "e.g. Why Choose Us" / >
< / div >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Features ( { data . features . length } ) < / label >
< button type = "button" onClick = { ( ) = > update ( { features : [ . . . data . features , { iconPath : '' , title : '' , description : '' } ] } ) } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Feature < / button >
< / div >
{ data . features . length === 0 && < p className = "text-sm text-[#666666] italic" > No features added yet . < / p > }
< div className = "space-y-3" >
{ data . features . map ( ( feat , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Feature { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > update ( { features : data.features.filter ( ( _ , i ) = > i !== idx ) } ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< input type = "text" className = { inputCls } value = { feat . iconPath } onChange = { ( e ) = > updateFeature ( idx , { iconPath : e.target.value } ) } placeholder = "Icon path (e.g. /assets/icons/icon.svg)" / >
< input type = "text" className = { inputCls } value = { feat . title } onChange = { ( e ) = > updateFeature ( idx , { title : e.target.value } ) } placeholder = "Feature title" / >
< textarea rows = { 2 } className = { textareaCls } value = { feat . description } onChange = { ( e ) = > updateFeature ( idx , { description : e.target.value } ) } placeholder = "Feature description" / >
< / div >
) ) }
< / div >
< / div >
) ;
}
function renderAboutTeamForm() {
const data = editContent as AboutTeamContent ;
const update = ( patch : Partial < AboutTeamContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
function updateMember ( index : number , patch : Partial < AboutTeamMember > ) {
const members = [ . . . data . members ] ;
members [ index ] = { . . . members [ index ] , . . . patch } ;
update ( { members } ) ;
}
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Section Title < / label >
< input type = "text" className = { inputCls } value = { data . title } onChange = { ( e ) = > update ( { title : e.target.value } ) } placeholder = "e.g. Meet the minds behind the platform." / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Section Subtitle < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . subtitle } onChange = { ( e ) = > update ( { subtitle : e.target.value } ) } placeholder = "Section description" / >
< / div >
< div className = "flex items-center justify-between mb-2" >
< label className = "block text-sm font-medium text-[#00293d]" > Team Members ( { data . members . length } ) < / label >
< button type = "button" onClick = { ( ) = > update ( { members : [ . . . data . members , { name : '' , role : '' , imageUrl : '' } ] } ) } className = "px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors" > + Add Member < / button >
< / div >
{ data . members . length === 0 && < p className = "text-sm text-[#666666] italic" > No team members added yet . < / p > }
< div className = "space-y-3" >
{ data . members . map ( ( member , idx ) = > (
< div key = { idx } className = "p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2" >
< div className = "flex items-center justify-between" >
< span className = "text-xs font-semibold text-[#666666]" > Member { idx + 1 } < / span >
< button type = "button" onClick = { ( ) = > update ( { members : data.members.filter ( ( _ , i ) = > i !== idx ) } ) } className = "text-red-500 hover:text-red-700 text-xs font-medium" > Remove < / button >
< / div >
< div className = "grid grid-cols-2 gap-2" >
< input type = "text" className = { inputCls } value = { member . name } onChange = { ( e ) = > updateMember ( idx , { name : e.target.value } ) } placeholder = "Name" / >
< input type = "text" className = { inputCls } value = { member . role } onChange = { ( e ) = > updateMember ( idx , { role : e.target.value } ) } placeholder = "Role" / >
< / div >
{ renderImageUploadField ( ` Photo ` , member . imageUrl , ( url ) = > updateMember ( idx , { imageUrl : url } ) ) }
< / div >
) ) }
< / div >
< / div >
) ;
}
function renderAboutCtaForm() {
const data = editContent as AboutCtaContent ;
const update = ( patch : Partial < AboutCtaContent > ) = > setEditContent ( { . . . data , . . . patch } ) ;
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Title < / label >
< input type = "text" className = { inputCls } value = { data . title } onChange = { ( e ) = > update ( { title : e.target.value } ) } placeholder = "e.g. Ready to find an agent?" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Description < / label >
< textarea rows = { 2 } className = { textareaCls } value = { data . description } onChange = { ( e ) = > update ( { description : e.target.value } ) } placeholder = "CTA description" / >
< / div >
< div >
< label className = "block text-sm font-medium text-[#00293d] mb-1" > Button Text < / label >
< input type = "text" className = { inputCls } value = { data . buttonText } onChange = { ( e ) = > update ( { buttonText : e.target.value } ) } placeholder = "e.g. Start Your Search" / >
< / div >
< / div >
) ;
}
2026-02-22 21:52:05 +05:30
function renderModalContent() {
if ( ! editingSection || editContent == null ) return null ;
2026-02-24 03:30:35 +05:30
const key = editingSection . sectionKey ;
const page = editingSection . pageSlug ;
if ( page === 'about' ) {
switch ( key ) {
case 'hero' : return renderAboutHeroForm ( ) ;
case 'stats' : return renderAboutStatsForm ( ) ;
case 'features' : return renderAboutFeaturesForm ( ) ;
case 'team' : return renderAboutTeamForm ( ) ;
case 'cta' : return renderAboutCtaForm ( ) ;
}
}
switch ( key ) {
2026-02-22 21:52:05 +05:30
case 'hero' : return renderHeroForm ( ) ;
case 'features' : return renderFeaturesForm ( ) ;
case 'topProfessionals' : return renderTopProfessionalsForm ( ) ;
case 'testimonials' : return renderTestimonialsForm ( ) ;
default : return < p className = "text-[#666666]" > Unknown section type . < / p > ;
}
}
// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------
2026-02-24 03:30:35 +05:30
const currentSections = activePage === 'landing' ? LANDING_SECTIONS : ABOUT_SECTIONS ;
const currentRecords = activePage === 'landing' ? records : aboutRecords ;
2026-02-22 21:52:05 +05:30
return (
< div >
{ /* Page header */ }
< div className = "mb-6" >
< h1 className = "text-2xl font-bold text-[#00293d] font-fractul" > Content Management < / h1 >
2026-02-24 03:30:35 +05:30
< p className = "text-[#666666] font-serif" > Manage website page content < / p >
< / div >
{ /* Page tabs */ }
< div className = "border-b border-[#e5e7eb] mb-6" >
< nav className = "flex -mb-px" >
< button
type = "button"
onClick = { ( ) = > setActivePage ( 'landing' ) }
className = { ` px-5 py-3 text-sm font-medium border-b-2 transition-colors ${ activePage === 'landing' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]' } ` }
>
Landing Page
< / button >
< button
type = "button"
onClick = { ( ) = > setActivePage ( 'about' ) }
className = { ` px-5 py-3 text-sm font-medium border-b-2 transition-colors ${ activePage === 'about' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]' } ` }
>
About Us
< / button >
< / nav >
2026-02-22 21:52:05 +05:30
< / div >
{ /* Notification toast */ }
{ notification && (
< div className = { ` mb-4 p-3 rounded-lg border text-sm ${ notification . type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700' } ` } >
{ notification . message }
< / div >
) }
{ /* Error */ }
{ error && (
< div className = "mb-4 p-3 bg-red-50 border border-red-200 rounded-lg" >
< p className = "text-red-700 text-sm" > { error } < / p >
< / div >
) }
{ /* Loading */ }
{ isLoading ? (
< div className = "flex items-center justify-center py-16" >
< div className = "animate-spin rounded-full h-8 w-8 border-b-2 border-[#f5a623]" > < / div >
< / div >
) : (
/* Section cards grid */
< div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
2026-02-24 03:30:35 +05:30
{ currentSections . map ( ( section ) = > {
const record = recordFor ( section . pageSlug , section . sectionKey ) ;
2026-02-22 21:52:05 +05:30
const hasContent = ! ! record ;
const isPublished = record ? . isPublished ? ? false ;
return (
2026-02-24 03:30:35 +05:30
< div key = { ` ${ section . pageSlug } - ${ section . sectionKey } ` } className = "bg-white rounded-xl shadow-sm border border-[#e5e7eb]" >
2026-02-22 21:52:05 +05:30
< div className = "px-6 py-4 border-b border-[#e5e7eb] flex items-center justify-between" >
< div >
< h2 className = "text-lg font-semibold text-[#00293d]" > { section . label } < / h2 >
< p className = "text-sm text-[#666666] font-serif" > { section . description } < / p >
< / div >
< div className = "flex items-center space-x-2" >
{ hasContent ? (
isPublished ? (
< span className = "px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800" > Published < / span >
) : (
< span className = "px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800" > Draft < / span >
)
) : (
< span className = "px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800" > No Content < / span >
) }
< / div >
< / div >
< div className = "px-6 py-4" >
{ hasContent ? (
< p className = "text-sm text-[#666666]" >
Last updated : { new Date ( record . updatedAt ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'short' , day : 'numeric' , hour : '2-digit' , minute : '2-digit' } ) }
< / p >
) : (
< p className = "text-sm text-[#666666] italic" > No content configured yet . Click edit to add content . < / p >
) }
< div className = "mt-4 flex justify-end" >
< button
2026-02-24 03:30:35 +05:30
onClick = { ( ) = > openEditor ( section ) }
2026-02-22 21:52:05 +05:30
className = "px-4 py-2 bg-[#f5a623] hover:bg-[#e09620] text-white text-sm font-medium rounded-lg transition-colors inline-flex items-center"
>
< svg className = "w-4 h-4 mr-1.5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" / >
< / svg >
Edit
< / button >
< / div >
< / div >
< / div >
) ;
} ) }
< / div >
) }
{ /* Edit modal */ }
{ editingSection && (
< div className = "fixed inset-0 bg-black/50 flex items-center justify-center z-50" >
< div className = "bg-white rounded-xl shadow-xl w-full max-w-2xl mx-4 max-h-[90vh] flex flex-col" >
{ /* Modal header */ }
< div className = "px-6 py-4 border-b border-[#e5e7eb] flex-shrink-0" >
< h3 className = "text-lg font-semibold text-[#00293d]" >
2026-02-24 03:30:35 +05:30
Edit { editingSection . label }
2026-02-22 21:52:05 +05:30
< / h3 >
2026-02-24 03:30:35 +05:30
< p className = "text-sm text-[#666666]" > { editingSection . description } < / p >
2026-02-22 21:52:05 +05:30
< / div >
{ /* Modal body - scrollable */ }
< div className = "px-6 py-4 overflow-y-auto flex-1" >
{ modalError && (
< div className = "mb-4 p-3 bg-red-50 border border-red-200 rounded-lg" >
< p className = "text-red-700 text-sm" > { modalError } < / p >
< / div >
) }
{ renderModalContent ( ) }
< / div >
{ /* Modal footer */ }
< div className = "px-6 py-4 border-t border-[#e5e7eb] flex justify-end space-x-3 flex-shrink-0" >
< button
type = "button"
onClick = { closeEditor }
className = "px-4 py-2 border border-[#e5e7eb] text-[#00293d] rounded-xl hover:bg-[#f5f9f8] transition-colors"
>
Cancel
< / button >
< button
type = "button"
onClick = { handleSave }
disabled = { isSubmitting }
className = "px-4 py-2 bg-[#f5a623] text-white rounded-xl hover:bg-[#e09620] transition-colors disabled:opacity-50"
>
{ isSubmitting ? 'Saving...' : 'Save Changes' }
< / button >
< / div >
< / div >
< / div >
) }
< / div >
) ;
}