OnboardingPortal
A full-screen, multi-step onboarding wizard that guides new accounts through business setup, phone number configuration, and hardware assignment.
Basic Usage
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>
);
}
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.
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:
| Step | Description | Substeps |
|---|---|---|
| Account | Business profile and team setup | Business Details, Team Members |
| Numbers | Phone number ordering and porting | Number options, ordering/porting flow, primary number, caller ID, directory listing |
| Hardware | Device assignment for team members | Device Assignment |
The portal has three view modes that users progress through:
- Splash screen — a welcome screen shown on first visit
- Overview — a dashboard showing progress across all steps with status indicators
- 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
| Prop | Type | Default | Description |
|---|---|---|---|
locale | Locale | English | Custom locale for UI strings |
formatting | FormattingOptions | - | Date/phone formatting options |
icons | ComponentIcons | - | Custom SVG icons |
collectionOptions | OnboardingCollectionOptions | - | Control which steps to show |
theme | 'light' | 'dark' | 'light' | Color theme |
appearance | { variables?: { colorPrimary?: string; colorPrimaryHover?: string } } | - | Brand color overrides |
canAcceptAgreement | boolean | true | Whether this session may accept the subscription agreement (see below) |
logoHtml | string | - | HTML string for sidebar logo |
platformName | string | - | Platform name shown in the UI |
onBack | () => void | - | Back button callback (shows button when provided) |
backLabel | string | - | Label for the back button |
fullTermsOfServiceUrl | string | - | Link to full terms of service |
recipientTermsOfServiceUrl | string | - | Link to recipient terms of service |
privacyPolicyUrl | string | - | Link to privacy policy |
documentationUrl | string | - | 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 |
className | string | - | Additional CSS class for the root element |
style | React.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:
<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
<OnboardingPortal
collectionOptions={{
steps: {
include: ['account', 'numbers'],
},
}}
/>
Exclude specific steps
<OnboardingPortal
collectionOptions={{
steps: {
exclude: ['hardware'],
},
}}
/>
OnboardingCollectionOptions
| Option | Type | Description |
|---|---|---|
steps.include | AccountOnboardingStep[] | Show only these steps |
steps.exclude | AccountOnboardingStep[] | 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:
| Value | Description |
|---|---|
'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:
<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
<OnboardingPortal theme="dark" />
The theme can also be set globally via the appearance.theme option in initialize(). The component-level theme prop takes precedence.
Navigation Callbacks
onBack
Provide an onBack callback to show a back button in the sidebar. Use this to let users navigate back to your application:
<OnboardingPortal onBack={() => router.push('/dashboard')} backLabel="Back to Dashboard" />
onStepChange
Track which step the user is on:
<OnboardingPortal
onStepChange={(event) => {
analytics.track('onboarding_step', { step: event.step });
}}
/>
Help and Support
The overview screen and sidebar can link to help resources:
<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 screenonScheduleCall— shows a "Schedule a Call" button on the overview screenonHelpSupport— shows a "Help & Support" link in the sidebar
Legal Links
Display terms and privacy links on the completion screen:
<OnboardingPortal
fullTermsOfServiceUrl="https://example.com/terms"
recipientTermsOfServiceUrl="https://example.com/recipient-terms"
privacyPolicyUrl="https://example.com/privacy"
/>
Complete Example
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
- DialstackComponentsProvider - Provider configuration
- Theming - Global appearance customization
- i18n - Internationalization