diff --git a/craft/src/components/basic/SearchBar.tsx b/craft/src/components/basic/SearchBar.tsx index 1f3977d..ad752d6 100644 --- a/craft/src/components/basic/SearchBar.tsx +++ b/craft/src/components/basic/SearchBar.tsx @@ -171,7 +171,7 @@ SearchBar.craft = { /* ---------- HTML export ---------- */ (SearchBar as any).toHtml = (props: SearchBarProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { placeholder = 'Search...', buttonText = 'Search', diff --git a/craft/src/components/forms/ContactForm.tsx b/craft/src/components/forms/ContactForm.tsx index fa2bbb7..a813e01 100644 --- a/craft/src/components/forms/ContactForm.tsx +++ b/craft/src/components/forms/ContactForm.tsx @@ -372,7 +372,7 @@ ContactForm.craft = { /* ---------- HTML export ---------- */ (ContactForm as any).toHtml = (props: ContactFormProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const formStyle = cssPropsToString({ padding: '32px', display: 'flex', diff --git a/craft/src/components/forms/InputField.tsx b/craft/src/components/forms/InputField.tsx index 8f74072..4c8a607 100644 --- a/craft/src/components/forms/InputField.tsx +++ b/craft/src/components/forms/InputField.tsx @@ -165,7 +165,7 @@ InputField.craft = { /* ---------- HTML export ---------- */ (InputField as any).toHtml = (props: InputFieldProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const wrapStyle = cssPropsToString({ display: 'flex', flexDirection: 'column', diff --git a/craft/src/components/forms/SubscribeForm.tsx b/craft/src/components/forms/SubscribeForm.tsx index 46c031b..d97df7e 100644 --- a/craft/src/components/forms/SubscribeForm.tsx +++ b/craft/src/components/forms/SubscribeForm.tsx @@ -249,7 +249,7 @@ SubscribeForm.craft = { /* ---------- HTML export ---------- */ (SubscribeForm as any).toHtml = (props: SubscribeFormProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { heading = 'Subscribe to our newsletter', placeholder = 'Enter your email', diff --git a/craft/src/components/forms/TextareaField.tsx b/craft/src/components/forms/TextareaField.tsx index 2cb7ae9..28e38ba 100644 --- a/craft/src/components/forms/TextareaField.tsx +++ b/craft/src/components/forms/TextareaField.tsx @@ -167,7 +167,7 @@ TextareaField.craft = { /* ---------- HTML export ---------- */ (TextareaField as any).toHtml = (props: TextareaFieldProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const wrapStyle = cssPropsToString({ display: 'flex', flexDirection: 'column', diff --git a/craft/src/components/sections/Accordion.tsx b/craft/src/components/sections/Accordion.tsx index 092777a..86bb0bd 100644 --- a/craft/src/components/sections/Accordion.tsx +++ b/craft/src/components/sections/Accordion.tsx @@ -293,7 +293,7 @@ Accordion.craft = { /* ---------- HTML export ---------- */ (Accordion as any).toHtml = (props: AccordionProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const sectionStyle = cssPropsToString({ padding: '60px 20px', ...props.style, diff --git a/craft/src/components/sections/CTASection.tsx b/craft/src/components/sections/CTASection.tsx index be97e98..94eb78a 100644 --- a/craft/src/components/sections/CTASection.tsx +++ b/craft/src/components/sections/CTASection.tsx @@ -173,7 +173,7 @@ CTASection.craft = { /* ---------- HTML export ---------- */ (CTASection as any).toHtml = (props: CTASectionProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const sectionStyle = cssPropsToString({ background: props.gradient || defaultGradient, padding: '80px 20px', diff --git a/craft/src/components/sections/CallToAction.tsx b/craft/src/components/sections/CallToAction.tsx index 8319465..8e7b64f 100644 --- a/craft/src/components/sections/CallToAction.tsx +++ b/craft/src/components/sections/CallToAction.tsx @@ -405,7 +405,7 @@ CallToAction.craft = { /* ---------- HTML export ---------- */ (CallToAction as any).toHtml = (props: CallToActionProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const bgType = props.bgType || 'gradient'; const bgValue = props.bgValue || defaultGradient; diff --git a/craft/src/components/sections/ContentSlider.tsx b/craft/src/components/sections/ContentSlider.tsx index 9318afe..070dfcd 100644 --- a/craft/src/components/sections/ContentSlider.tsx +++ b/craft/src/components/sections/ContentSlider.tsx @@ -443,7 +443,7 @@ ContentSlider.craft = { /* ---------- HTML export ---------- */ (ContentSlider as any).toHtml = (props: ContentSliderProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { slides = defaultSlides, autoplay = true, diff --git a/craft/src/components/sections/Countdown.tsx b/craft/src/components/sections/Countdown.tsx index 0c82546..42b981c 100644 --- a/craft/src/components/sections/Countdown.tsx +++ b/craft/src/components/sections/Countdown.tsx @@ -249,7 +249,7 @@ Countdown.craft = { /* ---------- HTML export ---------- */ (Countdown as any).toHtml = (props: CountdownProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { targetDate = DEFAULT_TARGET, heading = 'Coming Soon', diff --git a/craft/src/components/sections/FeaturesGrid.tsx b/craft/src/components/sections/FeaturesGrid.tsx index aa67c5e..6313f98 100644 --- a/craft/src/components/sections/FeaturesGrid.tsx +++ b/craft/src/components/sections/FeaturesGrid.tsx @@ -177,7 +177,7 @@ FeaturesGrid.craft = { /* ---------- HTML export ---------- */ (FeaturesGrid as any).toHtml = (props: FeaturesGridProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const sectionStyle = cssPropsToString({ padding: '80px 20px', ...props.style, diff --git a/craft/src/components/sections/Gallery.tsx b/craft/src/components/sections/Gallery.tsx index 93a238c..3cfd1c5 100644 --- a/craft/src/components/sections/Gallery.tsx +++ b/craft/src/components/sections/Gallery.tsx @@ -277,7 +277,7 @@ Gallery.craft = { /* ---------- HTML export ---------- */ (Gallery as any).toHtml = (props: GalleryProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const sectionStyle = cssPropsToString({ padding: '60px 20px', ...props.style, diff --git a/craft/src/components/sections/HeroSimple.tsx b/craft/src/components/sections/HeroSimple.tsx index afa9703..cac745b 100644 --- a/craft/src/components/sections/HeroSimple.tsx +++ b/craft/src/components/sections/HeroSimple.tsx @@ -404,7 +404,7 @@ HeroSimple.craft = { /* ---------- HTML export ---------- */ (HeroSimple as any).toHtml = (props: HeroProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const bg = buildBackground(props); const justifyMap: Record = { top: 'flex-start', center: 'center', bottom: 'flex-end' }; diff --git a/craft/src/components/sections/NumberCounter.tsx b/craft/src/components/sections/NumberCounter.tsx index 06b8892..bbd0fdc 100644 --- a/craft/src/components/sections/NumberCounter.tsx +++ b/craft/src/components/sections/NumberCounter.tsx @@ -305,7 +305,7 @@ NumberCounter.craft = { /* ---------- HTML export ---------- */ (NumberCounter as any).toHtml = (props: NumberCounterProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { counters = defaultCounters, columns = 4, diff --git a/craft/src/components/sections/PricingTable.tsx b/craft/src/components/sections/PricingTable.tsx index 8dc6bf6..8489155 100644 --- a/craft/src/components/sections/PricingTable.tsx +++ b/craft/src/components/sections/PricingTable.tsx @@ -398,7 +398,7 @@ PricingTable.craft = { /* ---------- HTML export ---------- */ (PricingTable as any).toHtml = (props: PricingTableProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const bulletType = props.bulletType || 'check'; const sectionStyle = cssPropsToString({ padding: '80px 20px', diff --git a/craft/src/components/sections/Tabs.tsx b/craft/src/components/sections/Tabs.tsx index e26c84e..165fecb 100644 --- a/craft/src/components/sections/Tabs.tsx +++ b/craft/src/components/sections/Tabs.tsx @@ -290,7 +290,7 @@ Tabs.craft = { /* ---------- HTML export ---------- */ (Tabs as any).toHtml = (props: TabsProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>'); + const esc = (s: any) => String(s ?? "").replace(//g, '>'); const sectionStyle = cssPropsToString({ padding: '60px 20px', ...props.style, diff --git a/craft/src/components/sections/Testimonials.tsx b/craft/src/components/sections/Testimonials.tsx index a6822a7..711f7b1 100644 --- a/craft/src/components/sections/Testimonials.tsx +++ b/craft/src/components/sections/Testimonials.tsx @@ -371,7 +371,7 @@ Testimonials.craft = { /* ---------- HTML export ---------- */ (Testimonials as any).toHtml = (props: TestimonialsProps, _childrenHtml: string) => { - const esc = (s: string) => s.replace(//g, '>').replace(/"/g, '"'); + const esc = (s: any) => String(s ?? "").replace(//g, '>').replace(/"/g, '"'); const { testimonials = defaultTestimonials, layout = 'grid', diff --git a/craft/src/panels/sitesmith/SitesmithModal.tsx b/craft/src/panels/sitesmith/SitesmithModal.tsx index c8d5e0f..8d55ce9 100644 --- a/craft/src/panels/sitesmith/SitesmithModal.tsx +++ b/craft/src/panels/sitesmith/SitesmithModal.tsx @@ -8,6 +8,7 @@ import { UpgradeBanner } from './UpgradeBanner'; import { ScopeConfirmDialog } from './ScopeConfirmDialog'; import { MessageList } from './MessageList'; import { ChatInput } from './ChatInput'; +import { WorkingIndicator } from './WorkingIndicator'; import { SitesmithResponse } from '../../types/sitesmith'; interface Props { onClose: () => void; } @@ -76,11 +77,15 @@ export const SitesmithModal: React.FC = ({ onClose }) => { : }
- + {busy ? ( + + ) : ( + + )}
{ + const [frame, setFrame] = useState(0); + const [phrase, setPhrase] = useState('Thinking'); + const [elapsed, setElapsed] = useState(0); + const [startTime] = useState(() => Date.now()); + + useEffect(() => { + const spinnerTimer = window.setInterval(() => { + setFrame((f) => (f + 1) % SPINNER.length); + }, 80); + const phraseTimer = window.setInterval(() => { + setPhrase(PHRASES[Math.floor(Math.random() * PHRASES.length)]); + }, 2500); + const elapsedTimer = window.setInterval(() => { + setElapsed(Math.floor((Date.now() - startTime) / 1000)); + }, 1000); + return () => { + window.clearInterval(spinnerTimer); + window.clearInterval(phraseTimer); + window.clearInterval(elapsedTimer); + }; + }, [startTime]); + + return ( +
+ {SPINNER[frame]} + {phrase}… + ({elapsed}s) +
+ ); +}; + +const containerStyle: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: 10, + padding: '14px 4px', + fontSize: 14, +}; +const spinnerStyle: React.CSSProperties = { + color: '#8b5cf6', + fontSize: 18, + width: 18, + display: 'inline-block', + textAlign: 'center', + fontFamily: 'monospace', +}; +const phraseStyle: React.CSSProperties = { + color: '#e4e4e7', + fontStyle: 'italic', + fontWeight: 500, +}; +const metaStyle: React.CSSProperties = { + color: '#71717a', + fontSize: 12, + marginLeft: 'auto', +};