feat(onboarding): ChatGPT side door on the Connect to Claude step (#1981)

The checklist's closing step keeps Anslut till Claude as the one-click primary and gains a collapsed Anvander du ChatGPT? expander with the manual custom-connector path (Settings > Apps > Advanced settings > Developer mode) and a copy-server-address button built from the page origin. Expanding fires onboarding_setup_step_started { step: 'chatgpt' }.
This commit is contained in:
Jakob Wennberg
2026-08-31 09:25:58 +01:00
committed by GitHub
parent 8b0aa80ea0
commit d67879b786
3 changed files with 62 additions and 2 deletions
@@ -92,6 +92,10 @@ export default function NewUserChecklist({
// step (companies whose completedAt arrives from the server never see it).
const [retiring, setRetiring] = useState<'verdict' | 'closing' | 'done' | null>(null)
const retireStartedRef = useRef(false)
// The ChatGPT side door on the Claude step: collapsed by default so the
// one-click Claude path stays the visual primary.
const [chatGptOpen, setChatGptOpen] = useState(false)
const [serverUrlCopied, setServerUrlCopied] = useState(false)
const hasMigration = ENABLED_EXTENSION_IDS.has('arcim-migration')
const hasBanking = ENABLED_EXTENSION_IDS.has('enable-banking')
@@ -231,6 +235,24 @@ export default function NewUserChecklist({
const link = `https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=${encodeURIComponent(appName)}&connectorUrl=${encodeURIComponent(serverUrl)}`
window.open(link, '_blank', 'noopener')
}
// ChatGPT has no add-connector deep link (the user pastes the server URL
// into Developer mode manually), so the side door copies the URL instead.
const toggleChatGpt = () => {
setChatGptOpen((open) => {
if (!open) captureSetup('onboarding_setup_step_started', { step: 'chatgpt' })
return !open
})
}
const copyServerUrl = async () => {
const serverUrl = `${window.location.origin}/api/extensions/ext/mcp-server/mcp?tool_namespace=accounted`
try {
await navigator.clipboard.writeText(serverUrl)
setServerUrlCopied(true)
window.setTimeout(() => setServerUrlCopied(false), 2000)
} catch {
// Clipboard access can be denied; the button simply stays on "Kopiera".
}
}
const dismiss = () =>
void persist({ dismissed: true }, 'dismiss').then((updated) => {
if (updated) captureSetup('onboarding_setup_dismissed', {})
@@ -404,6 +426,30 @@ export default function NewUserChecklist({
{t('step_claude_action')}
</Button>
)}
footnote={
<div className="mt-2">
<button
type="button"
onClick={toggleChatGpt}
aria-expanded={chatGptOpen}
className="text-xs text-muted-foreground underline decoration-border underline-offset-4 transition-colors hover:text-foreground"
>
{t('step_claude_chatgpt_link')}
</button>
{chatGptOpen && (
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2">
<p className="max-w-prose text-xs leading-5 text-muted-foreground">
{t('step_claude_chatgpt_steps', { appName })}
</p>
<Button size="sm" variant="outline" onClick={() => void copyServerUrl()}>
{serverUrlCopied
? t('step_claude_chatgpt_copied')
: t('step_claude_chatgpt_copy')}
</Button>
</div>
)}
</div>
}
>
{t('step_claude_description')}
</Step>
@@ -431,6 +477,7 @@ function Step({
action,
marks,
doneNote,
footnote,
last = false,
children,
}: {
@@ -445,6 +492,10 @@ function Step({
* exception to "done steps collapse to their title" (e.g. the bank step's
* sweep outcome, which is the payoff the migrator is waiting for). */
doneNote?: React.ReactNode
/** Block content below the pitch while the step is open. Unlike `children`
* (which lives inside a <p>), this may hold nested block elements, e.g.
* the Claude step's ChatGPT side door. */
footnote?: React.ReactNode
last?: boolean
children: React.ReactNode
}) {
@@ -502,6 +553,7 @@ function Step({
{marks && <span className="flex items-center gap-2">{marks}</span>}
</div>
)}
{open && footnote}
</div>
</li>
)
+5 -1
View File
@@ -1543,7 +1543,11 @@
"step_receipts_channels_no_wa": "Email · upload",
"step_claude_title": "Connect to Claude",
"step_claude_description": "Do your bookkeeping from the chat: drop receipts and SIE files, reconcile the tax account and ask about your numbers. Everything is already connected.",
"step_claude_action": "Connect to Claude"
"step_claude_action": "Connect to Claude",
"step_claude_chatgpt_link": "Using ChatGPT?",
"step_claude_chatgpt_steps": "In ChatGPT (Plus, Pro or Business): Settings → Apps → Advanced settings → turn on Developer mode. Choose Add custom connector, name it {appName} and paste the server address. Then start a new chat and ask for a walkthrough of your books.",
"step_claude_chatgpt_copy": "Copy server address",
"step_claude_chatgpt_copied": "Copied"
},
"tax_assessment_notices": {
"title": "Final tax notices and remaining tax",
+5 -1
View File
@@ -1543,7 +1543,11 @@
"step_receipts_channels_no_wa": "Mejl · uppladdning",
"step_claude_title": "Anslut till Claude",
"step_claude_description": "Bokför via chatten: släpp kvitton och SIE-filer, stäm av skattekontot och ställ frågor om siffrorna. Allt är redan kopplat.",
"step_claude_action": "Anslut till Claude"
"step_claude_action": "Anslut till Claude",
"step_claude_chatgpt_link": "Använder du ChatGPT?",
"step_claude_chatgpt_steps": "I ChatGPT (Plus, Pro eller Business): Inställningar → Appar → Avancerade inställningar → slå på Utvecklarläge. Välj Lägg till anpassad connector, namnge den {appName} och klistra in serveradressen. Starta sedan en ny chatt och be om en genomgång av bokföringen.",
"step_claude_chatgpt_copy": "Kopiera serveradress",
"step_claude_chatgpt_copied": "Kopierad"
},
"tax_assessment_notices": {
"title": "Slutskattebesked och kvarskatt",