Sitesmith: AI site builder addon (frontend) #1

Merged
jknapp merged 10 commits from sitesmith-ai-builder into main 2026-05-24 17:11:03 +00:00
2 changed files with 41 additions and 0 deletions
Showing only changes of commit bf55ee85b9 - Show all commits

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useSitesmith } from '../../hooks/useSitesmith';
import { useEditorConfig } from '../../state/EditorConfigContext';
interface Props { onClick: () => void; }
export const SitesmithButton: React.FC<Props> = ({ onClick }) => {
const cfg = useEditorConfig();
const siteId = cfg.whpConfig?.siteId ?? 0;
const { summary } = useSitesmith(siteId);
const locked = summary?.status === 'DISABLED';
const capped = summary?.status === 'CAP_REACHED';
return (
<button
type="button"
onClick={onClick}
className="topbar-btn sitesmith-btn"
title={locked ? 'Sitesmith — paid addon (click to learn more)' : 'Sitesmith AI Builder'}
style={{
background: locked ? '#1f1f24' : 'linear-gradient(135deg, #6366f1, #8b5cf6)',
color: '#fff', border: 'none', padding: '6px 12px', borderRadius: 6, cursor: 'pointer', fontWeight: 500,
}}
>
Sitesmith
{locked && <span aria-hidden style={{ marginLeft: 6, fontSize: 12 }}>🔒</span>}
{capped && !locked && <span aria-hidden style={{ marginLeft: 6, fontSize: 11, opacity: 0.85 }}>(cap)</span>}
</button>
);
};

View File

@@ -6,6 +6,7 @@ import { usePages } from '../../state/PageContext';
import { DeviceMode } from '../../types'; import { DeviceMode } from '../../types';
import { TemplateModal } from './TemplateModal'; import { TemplateModal } from './TemplateModal';
import { HeadCodeModal } from './HeadCodeModal'; import { HeadCodeModal } from './HeadCodeModal';
import { SitesmithButton } from '../sitesmith/SitesmithButton';
interface TopBarProps { interface TopBarProps {
device: DeviceMode; device: DeviceMode;
@@ -26,6 +27,7 @@ export const TopBar: React.FC<TopBarProps> = ({ device, onDeviceChange }) => {
const [isDraft, setIsDraft] = useState(false); const [isDraft, setIsDraft] = useState(false);
const [templateModalOpen, setTemplateModalOpen] = useState(false); const [templateModalOpen, setTemplateModalOpen] = useState(false);
const [headCodeModalOpen, setHeadCodeModalOpen] = useState(false); const [headCodeModalOpen, setHeadCodeModalOpen] = useState(false);
const [sitesmithOpen, setSitesmithOpen] = useState(false);
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const publishTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const publishTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const hasLoadedRef = useRef(false); const hasLoadedRef = useRef(false);
@@ -239,6 +241,8 @@ export const TopBar: React.FC<TopBarProps> = ({ device, onDeviceChange }) => {
</span> </span>
)} )}
<SitesmithButton onClick={() => setSitesmithOpen(true)} />
<button <button
className="topbar-btn primary" className="topbar-btn primary"
onClick={handleSave} onClick={handleSave}
@@ -269,6 +273,14 @@ export const TopBar: React.FC<TopBarProps> = ({ device, onDeviceChange }) => {
</div> </div>
<TemplateModal open={templateModalOpen} onClose={() => setTemplateModalOpen(false)} /> <TemplateModal open={templateModalOpen} onClose={() => setTemplateModalOpen(false)} />
<HeadCodeModal open={headCodeModalOpen} onClose={() => setHeadCodeModalOpen(false)} /> <HeadCodeModal open={headCodeModalOpen} onClose={() => setHeadCodeModalOpen(false)} />
{sitesmithOpen && (
<div role="dialog" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.6)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ background: '#0f0f17', color: '#fff', padding: 32, borderRadius: 10 }}>
<p>SitesmithModal will land in Task 20.</p>
<button onClick={() => setSitesmithOpen(false)} style={{ background: '#7c3aed', color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6 }}>Close</button>
</div>
</div>
)}
</nav> </nav>
); );
}; };