Render research CTA via @lobbyside/react when widget is online#884
Conversation
Adds the headless @lobbyside/react hook so the research CTA can be
rendered by our own component against live widget state, while keeping
the existing book-a-call card as the fallback.
Behavior:
- Online and queue open: host avatar, ctaText, name + title, buttonText;
the entire card is a click target that calls joinCall({ visitor })
with the logged-in user's name / email / company (org) / github when
available, and opens the returned entryUrl in a new tab.
- Offline / error / queue full: continues to render the existing
"Be part of the next supermemory app" card with the Book a call link
to cal.com/supermemory/growth.
- Loading: renders nothing, so we don't flash the fallback card before
the widget resolves.
- Dismiss X preserved (localStorage sm_next_app_research_cta_dismissed_v1).
Analytics: adds next_app_research_cta_lobbyside_call_clicked; the
existing next_app_research_cta_book_call_clicked is retained for the
book-a-call path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| const joinArgs = | ||
| Object.keys(visitor).length > 0 ? { visitor } : undefined | ||
| const { entryUrl } = await widget.joinCall(joinArgs) | ||
| window.open(entryUrl, "_blank", "noopener,noreferrer") |
There was a problem hiding this comment.
Popup blocker will silently drop the new tab on Safari/iOS and stricter browsers.
window.open(entryUrl, ...) is called after await widget.joinCall(), which means the user-activation gesture has already been consumed by the time the window opens. Browsers that enforce user-activation for window.open (Safari, Firefox with strict settings) will block the popup — but the queue entry has already been created, so the host sees a queued caller who never arrives.
Fix: open a blank window synchronously on click, then redirect it after joinCall() resolves:
const win = window.open('', '_blank', 'noopener,noreferrer')
const { entryUrl } = await widget.joinCall(joinArgs)
if (win) win.location.href = entryUrlOr navigate the current tab (window.location.href = entryUrl) if opening a new tab is not strictly required.
| const { entryUrl } = await widget.joinCall(joinArgs) | ||
| window.open(entryUrl, "_blank", "noopener,noreferrer") | ||
| } catch (err) { | ||
| console.error("[Lobbyside] joinCall failed", err) |
There was a problem hiding this comment.
joinCall() failures are silently swallowed — the user gets no feedback and the card stays in its "online" state.
The Lobbyside SDK explicitly throws QUEUE_FULL and INACTIVE errors, both of which can happen as a race after the card rendered as clickable (e.g. the queue fills or the host goes offline between render and click). Right now those cases log to the console and do nothing visible.
Consider branching on err.code and falling back to the book-a-call card (or showing an inline error) so the user isn't left with a broken click:
} catch (err: unknown) {
console.error('[Lobbyside] joinCall failed', err)
const code = (err as { code?: string })?.code
if (code === 'QUEUE_FULL' || code === 'INACTIVE') {
// force fallback to the book-a-call card
setWidgetError(true)
}
}- Size the host avatar at 36x36 so the face reads from the corner of the screen. - Add an 8px green online dot in the bottom-right corner of the avatar, ringed in the card background color. - Wrap Phone and Users icons in matching 36x36 flex containers so the center "×" glyph stays on the decorative cross lines in both the online (avatar) and offline (Users) layouts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
Two issues flagged on the CTA's click handler: - window.open(entryUrl) was called after await joinCall(), so Safari/iOS and other browsers that enforce user-activation for popups silently blocked the new tab while the queue entry had already been created — the host saw a caller who never arrived. Open a blank tab synchronously on click and redirect it once joinCall resolves. - joinCall() errors (QUEUE_FULL and INACTIVE can race after the card rendered as clickable, NETWORK is possible anytime) were only logged, leaving the user with a dead click. Redirect the pending tab to the cal.com book-a-call URL in the catch so the CTA always lands somewhere useful, matching the behavior of the offline / queue-full fallback card. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pinned to 0.2.0. The only behavior change we rely on is still covered by the existing status/isQueueFull/joinCall surface; no consumer-side code change is needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Adds the headless
@lobbyside/reacthook so the research CTA can be rendered by our own component against live widget state, while keeping the existing book-a-call card as the fallback.Behavior
ctaText, name + title,buttonText; the entire card is a click target that callsjoinCall({ visitor })with the logged-in user'sname/email/company(org) /githubwhen available, and opens the returnedentryUrlin a new tab.cal.com/supermemory/growth.sm_next_app_research_cta_dismissed_v1).Analytics
next_app_research_cta_lobbyside_call_clicked.next_app_research_cta_book_call_clickedis retained for the book-a-call path.How it looks
When online:
(You control the name, avatar, text, etc. from within the Lobbyside dashboard).
When offline:
(We'll soon add support for fallback data. Once that's live, you can also manage the offline strings on Lobbyside directly - more convenient to edit!).
Pre-filled visitor info
When a guest opens the join call link, their email, name etc is pre-filled from the auth context. I wasn't able to test this locally since I didn't have the proper local setup!
🤖 Generated with Claude Code