Skip to main content

OnboardingPortal

A full-screen, multi-step onboarding wizard that guides new accounts through business setup, phone number configuration, and hardware assignment.

Basic Usage

TypeScript
import { DialstackComponentsProvider } from '@dialstack/sdk-react';
import { OnboardingPortal } from '@dialstack/sdk-react/onboarding';

function OnboardingPage({ dialstack, clientSecret }) {
return (
<DialstackComponentsProvider dialstack={dialstack} clientSecret={clientSecret}>
<OnboardingPortal />
</DialstackComponentsProvider>
);
}
Component and provider come from different paths

Each component has its own import path, so your app only pulls in what it uses. OnboardingPortal comes from @dialstack/sdk-react/onboarding, while the provider and hooks come from the @dialstack/sdk-react root.

Automatic Progress Saving

The portal automatically saves onboarding progress to the server. Users can leave and return at any time — the wizard resumes where they left off.

How It Works

The onboarding portal presents three main steps, each with its own substeps:

StepDescriptionSubsteps
AccountBusiness profile and team setupBusiness Details, Team Members
NumbersPhone number ordering and portingNumber options, ordering/porting flow, primary number, caller ID, directory listing
HardwareDevice assignment for team membersDevice Assignment

The portal has three view modes that users progress through:

  1. Splash screen — a welcome screen shown on first visit
  2. Overview — a dashboard showing progress across all steps with status indicators
  3. Wizard — the step-by-step form workflow

When all steps are complete, a celebration screen is shown. After that, the overview screen serves as the landing page where users can review or revise any step.

Props

PropTypeDefaultDescription
localeLocaleEnglishCustom locale for UI strings
formattingFormattingOptions-Date/phone formatting options
iconsComponentIcons-Custom SVG icons
collectionOptionsOnboardingCollectionOptions-Control which steps to show
theme'light' | 'dark''light'Color theme
appearance{ variables?: { colorPrimary?: string; colorPrimaryHover?: string } }-Brand color overrides
canAcceptAgreementbooleantrueWhether this session may accept the subscription agreement (see below)
logoHtmlstring-HTML string for sidebar logo
platformNamestring-Platform name shown in the UI
onBack() => void-Back button callback (shows button when provided)
backLabelstring-Label for the back button
fullTermsOfServiceUrlstring-Link to full terms of service
recipientTermsOfServiceUrlstring-Link to recipient terms of service
privacyPolicyUrlstring-Link to privacy policy
documentationUrlstring-Link shown on the overview screen
onScheduleCall() => void-Callback for "Schedule a Call" on the overview screen
onHelpSupport() => void-Callback for "Help & Support" in the sidebar
onStepChange(event: { step: AccountOnboardingStep }) => void-Called when the active step changes
classNamestring-Additional CSS class for the root element
styleReact.CSSProperties-Inline styles for the root element

The Subscription Agreement Gate

Before an account can place calls it must accept the DialStack Service Subscription Agreement. The portal enforces this as a blocking screen: until the agreement is accepted, it replaces the whole portal.

Accepting is a capability on the session, not something the API infers about the signed-in person. A session may accept only if you enabled the agreement_acceptance component when you minted it — see Account Sessions. account_onboarding alone lets the session read the agreement and render the screen, but not sign it.

Set canAcceptAgreement to the same condition you used at mint:

TypeScript
<OnboardingPortal canAcceptAgreement={userMayAcceptForThisAccount} />

When it is false, the screen still renders the full agreement — so the reader learns what is pending and that service is blocked — but the affirmation and the Accept button are replaced by a note asking the account owner to accept.

The prop defaults to true, so an existing integration keeps working unchanged. It is a user-experience control, not a security boundary: if the session lacks the component, the submission is rejected regardless. Leaving it true for a session that cannot sign simply strands the user on a button that cannot succeed.

Controlling Steps

Use collectionOptions to show or hide specific steps. The final completion screen is always shown regardless of these options.

Show only specific steps

TypeScript
<OnboardingPortal
collectionOptions={{
steps: {
include: ['account', 'numbers'],
},
}}
/>

Exclude specific steps

TypeScript
<OnboardingPortal
collectionOptions={{
steps: {
exclude: ['hardware'],
},
}}
/>

OnboardingCollectionOptions

OptionTypeDescription
steps.includeAccountOnboardingStep[]Show only these steps
steps.excludeAccountOnboardingStep[]Hide these steps

When both include and exclude are set, include is applied first, then exclude removes from that result.

AccountOnboardingStep

The available step values are:

ValueDescription
'account'Business details and team members
'numbers'Phone number ordering, porting, and configuration
'hardware'Device assignment
'final_complete'Completion screen (always shown)

White-Labeling

Customize the portal's branding to match your platform:

TypeScript
<OnboardingPortal
platformName="Acme Telecom"
logoHtml={`
<div style="display:flex;align-items:center;gap:8px">
<img src="/logo.svg" width="28" height="24" alt="" />
<span style="font-size:20px;font-weight:700;color:#f0f0ff">
Acme Telecom
</span>
</div>
`}
appearance={{
variables: {
colorPrimary: '#00A67E',
colorPrimaryHover: '#008F6D',
},
}}
/>

The appearance.variables colors are applied to the sidebar, buttons, and accent elements throughout the wizard. When not provided, the portal uses the colors set via initialize().

Dark Theme

TypeScript
<OnboardingPortal theme="dark" />

The theme can also be set globally via the appearance.theme option in initialize(). The component-level theme prop takes precedence.

onBack

Provide an onBack callback to show a back button in the sidebar. Use this to let users navigate back to your application:

TypeScript
<OnboardingPortal onBack={() => router.push('/dashboard')} backLabel="Back to Dashboard" />

onStepChange

Track which step the user is on:

TypeScript
<OnboardingPortal
onStepChange={(event) => {
analytics.track('onboarding_step', { step: event.step });
}}
/>

Help and Support

The overview screen and sidebar can link to help resources:

TypeScript
<OnboardingPortal
documentationUrl="https://docs.example.com/onboarding"
onScheduleCall={() => {
window.open('https://calendly.com/example/onboarding', '_blank');
}}
onHelpSupport={() => {
window.Intercom('showNewMessage', 'I need help with onboarding');
}}
/>
  • documentationUrl — shows a "View Documentation" link on the overview screen
  • onScheduleCall — shows a "Schedule a Call" button on the overview screen
  • onHelpSupport — shows a "Help & Support" link in the sidebar

Display terms and privacy links on the completion screen:

TypeScript
<OnboardingPortal
fullTermsOfServiceUrl="https://example.com/terms"
recipientTermsOfServiceUrl="https://example.com/recipient-terms"
privacyPolicyUrl="https://example.com/privacy"
/>

Complete Example

TypeScript
import { initialize } from '@dialstack/sdk-js';
import { DialstackComponentsProvider } from '@dialstack/sdk-react';
import { OnboardingPortal } from '@dialstack/sdk-react/onboarding';
import { useEffect, useState } from 'react';

const dialstack = initialize({
publishableKey: 'pk_live_YOUR_KEY',
appearance: {
theme: 'light',
},
});

function OnboardingApp() {
const [clientSecret, setClientSecret] = useState<string | null>(null);

useEffect(() => {
fetch('/api/dialstack/session', { method: 'POST' })
.then((res) => res.json())
.then((data) => setClientSecret(data.client_secret));
}, []);

if (!clientSecret) return <div>Loading...</div>;

return (
<DialstackComponentsProvider dialstack={dialstack} clientSecret={clientSecret}>
<OnboardingPortal
platformName="Acme Telecom"
logoHtml={`
<div style="display:flex;align-items:center;gap:8px">
<img src="/logo.svg" width="28" height="24" alt="" />
<span style="font-size:20px;font-weight:700;color:#f0f0ff">
Acme Telecom
</span>
</div>
`}
appearance={{
variables: {
colorPrimary: '#6772E5',
colorPrimaryHover: '#5469D4',
},
}}
collectionOptions={{
steps: { exclude: ['hardware'] },
}}
onBack={() => (window.location.href = '/dashboard')}
backLabel="Back to Dashboard"
documentationUrl="https://docs.example.com/setup"
onScheduleCall={() => window.open('https://calendly.com/example')}
onHelpSupport={() => window.Intercom?.('show')}
onStepChange={(event) => {
console.log('Step changed:', event.step);
}}
fullTermsOfServiceUrl="https://example.com/terms"
privacyPolicyUrl="https://example.com/privacy"
/>
</DialstackComponentsProvider>
);
}

Next Steps