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';

// `dialstack` comes from loadDialstackAndInitialize(); see Setup
function OnboardingPage({ dialstack }) {
return (
<DialstackComponentsProvider dialstack={dialstack}>
<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 Team Members substep lists the account's users. It also lists portal administrators who have no phone service, which is where the account owner starts. Each of them has a Give phone access button that adds them as a user with the next free extension. Until that happens, they don't count toward the one team member the substep requires. Administrators are marked Admin, and removing one only takes away their phone access: they stay an administrator and go back to the list without phone service.

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 passed to loadDialstackAndInitialize().

Dark Theme​

TypeScript
<OnboardingPortal theme="dark" />

The theme can also be set globally via the appearance.theme option of loadDialstackAndInitialize(). 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 { useEffect, useState } from 'react';
import { loadDialstackAndInitialize, type DialStackInstance } from '@dialstack/sdk-js';
import { DialstackComponentsProvider } from '@dialstack/sdk-react';
import { OnboardingPortal } from '@dialstack/sdk-react/onboarding';

const dialstackPromise = loadDialstackAndInitialize({
publishableKey: 'pk_live_YOUR_KEY',
fetchClientSecret: async () => {
const res = await fetch('/api/dialstack/session', { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
},
appearance: {
theme: 'light',
},
});

function OnboardingApp() {
const [dialstack, setDialstack] = useState<DialStackInstance | null>(null);

useEffect(() => {
dialstackPromise.then(setDialstack);
}, []);

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

return (
<DialstackComponentsProvider dialstack={dialstack}>
<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​

  • Setup - Initialize the SDK and add the provider
  • Theming - Global appearance customization
  • i18n - Internationalization