fix(sitesmith): first-run wizard only for users who can use Sitesmith
The business-profile wizard was forced on an empty canvas even when Sitesmith was locked (paid addon), capped or switched off. The decision now waits for the entitlement and skips the wizard unless the user can chat (OK_MONTHLY / OK_BONUS); a failed entitlement falls back to the modal. useSitesmith reports an ok:false entitlement as an error so it cannot hang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92c88496a7
commit
84bc626ed0
3 files changed
+28
-9
No files matched your search
@@ -24,6 +24,7 @@ export function useSitesmith(siteId: number) {
|
||||
const r = await fetch(`${apiBase(whpConfig.apiUrl)}?action=entitlement`, { credentials: 'include' });
|
||||
const j = await r.json();
|
||||
if (j.ok) { setSummary(j.summary); profileSupported.current = j.profile_supported === true; }
|
||||
else setError(j.error || 'Sitesmith entitlement unavailable');
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
}, [whpConfig]);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ let notices: ReturnType<typeof useNotices>;
|
||||
let extras: ReturnType<typeof useProjectExtras>;
|
||||
let sendBodies: any[];
|
||||
let sendReply: any;
|
||||
let entitlementStatus: string;
|
||||
const onClose = vi.fn();
|
||||
|
||||
const Spy: React.FC = () => { notices = useNotices(); extras = useProjectExtras(); return null; };
|
||||
@@ -57,6 +58,7 @@ beforeEach(() => {
|
||||
__resetCommandsForTests();
|
||||
onClose.mockReset();
|
||||
sendBodies = [];
|
||||
entitlementStatus = 'OK_MONTHLY';
|
||||
sendReply = {
|
||||
ok: true,
|
||||
response: { type: 'patch', message: 'done', ops: [{
|
||||
@@ -69,7 +71,7 @@ beforeEach(() => {
|
||||
vi.stubGlobal('fetch', vi.fn(async (url: string, init?: any) => {
|
||||
const json = (v: unknown) => ({ json: async () => v });
|
||||
if (url.includes('action=send')) { sendBodies.push(JSON.parse(init.body)); return json(sendReply); }
|
||||
if (url.includes('action=entitlement')) return json({ ok: true, summary: { enabled: true, status: 'OK_MONTHLY', monthly_used: 0, monthly_cap: 10, bonus_credits: 0 } });
|
||||
if (url.includes('action=entitlement')) return json({ ok: true, summary: { enabled: true, status: entitlementStatus, monthly_used: 0, monthly_cap: 10, bonus_credits: 0 } });
|
||||
return json({ ok: true, messages: [] });
|
||||
}));
|
||||
});
|
||||
@@ -130,6 +132,15 @@ describe('first run and commands', () => {
|
||||
expect(document.querySelector('.sitesmith-wiz')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('empty canvas + no profile, but Sitesmith is locked: no wizard, the modal (upgrade path) shows', async () => {
|
||||
entitlementStatus = 'DISABLED';
|
||||
await mount(false, null, '');
|
||||
await flush();
|
||||
expect(document.querySelector('.sitesmith-wiz')).toBeNull();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
expect(document.querySelector('[role="dialog"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('a canvas with content is not interrupted by the wizard', async () => {
|
||||
await mount(false, null);
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
|
||||
@@ -34,7 +34,7 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
const { notify } = useNotices();
|
||||
const { open: reopen } = useSitesmithModal();
|
||||
const profile = useSitesmithProfile();
|
||||
const { summary, messages, send, loading, clearHistory } = useSitesmith(siteId);
|
||||
const { summary, messages, send, loading, clearHistory, error: entitlementError } = useSitesmith(siteId);
|
||||
const apply = useApplyAiResponse();
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [pendingReplace, setPendingReplace] = useState<SitesmithResponse | null>(null);
|
||||
@@ -51,18 +51,25 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
// First-run: an empty canvas with no business profile asks the questions first,
|
||||
// then comes back here with a starter message. Keep the prefill for this mount.
|
||||
const [prefill] = useState(() => takeSitesmithPrefill());
|
||||
const wantsWizard = useRef(
|
||||
// Only for users who can actually use Sitesmith: decided once the
|
||||
// entitlement arrives (a locked/capped user gets the modal's upgrade path).
|
||||
const [wantsWizard, setWantsWizard] = useState(() =>
|
||||
!!cfg.whpConfig && !target && !profile && !prefill && !wizardWasSkipped()
|
||||
&& (query.node('ROOT').get().data.nodes ?? []).length === 0,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!wantsWizard.current) return;
|
||||
onClose();
|
||||
openWizardThenSitesmith(() => reopen());
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const canChat = summary && (summary.status === 'OK_BONUS' || summary.status === 'OK_MONTHLY');
|
||||
|
||||
const wizardFired = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!wantsWizard || wizardFired.current) return;
|
||||
if (entitlementError || (summary && !canChat)) { setWantsWizard(false); return; }
|
||||
if (!summary) return;
|
||||
wizardFired.current = true;
|
||||
onClose();
|
||||
openWizardThenSitesmith(() => reopen());
|
||||
}, [wantsWizard, summary, canChat, entitlementError]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const panel: React.CSSProperties = { background: '#0f0f17', border: '1px solid #27272a', borderRadius: 12, width: 'min(720px, 90vw)', maxHeight: '90vh', display: 'flex', flexDirection: 'column' };
|
||||
const header: React.CSSProperties = { display: 'flex', alignItems: 'center', padding: '14px 18px', borderBottom: '1px solid #27272a', gap: 8 };
|
||||
const body: React.CSSProperties = { flex: 1, padding: '14px 18px', overflowY: 'auto', display: 'flex', flexDirection: 'column' };
|
||||
@@ -148,7 +155,7 @@ export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
if (!r.ok) setError(r.error || 'Failed to clear history');
|
||||
};
|
||||
|
||||
if (wantsWizard.current) return null;
|
||||
if (wantsWizard) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
Reference in new issue
Block a user