feat: revamp onboarding (#9068)

* feat: revamp onboarding, send list to mixpanel, join logic to convert to single string

* chore: props changes

* fix: allow user to proceed even if api fails

* chore: remove console.log

* chore: remove commented code

* chore: minor colour tweaks

* chore: resolve comments
This commit is contained in:
manika-signoz 2025-09-23 20:47:39 +05:30 committed by GitHub
parent b2dc2790d8
commit 73ff89a80a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 177 additions and 161 deletions

View File

@ -2,14 +2,14 @@
import '../OnboardingQuestionaire.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Input, Typography } from 'antd';
import { Button, Checkbox, Input, Typography } from 'antd';
import TextArea from 'antd/lib/input/TextArea';
import logEvent from 'api/common/logEvent';
import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react';
import { useEffect, useState } from 'react';
export interface SignozDetails {
interestInSignoz: string | null;
interestInSignoz: string[] | null;
otherInterestInSignoz: string | null;
discoverSignoz: string | null;
}
@ -22,9 +22,12 @@ interface AboutSigNozQuestionsProps {
}
const interestedInOptions: Record<string, string> = {
savingCosts: 'Saving costs',
otelNativeStack: 'Interested in Otel-native stack',
allInOne: 'All in one (Logs, Metrics & Traces)',
loweringCosts: 'Lowering observability costs',
otelNativeStack: 'Interested in OTel-native stack',
deploymentFlexibility: 'Deployment flexibility (Cloud/Self-Host) in future',
singleTool:
'Single Tool (logs, metrics & traces) to reduce operational overhead',
correlateSignals: 'Correlate signals for faster troubleshooting',
};
export function AboutSigNozQuestions({
@ -33,8 +36,8 @@ export function AboutSigNozQuestions({
onNext,
onBack,
}: AboutSigNozQuestionsProps): JSX.Element {
const [interestInSignoz, setInterestInSignoz] = useState<string | null>(
signozDetails?.interestInSignoz || null,
const [interestInSignoz, setInterestInSignoz] = useState<string[]>(
signozDetails?.interestInSignoz || [],
);
const [otherInterestInSignoz, setOtherInterestInSignoz] = useState<string>(
signozDetails?.otherInterestInSignoz || '',
@ -47,8 +50,8 @@ export function AboutSigNozQuestions({
useEffect((): void => {
if (
discoverSignoz !== '' &&
interestInSignoz !== null &&
(interestInSignoz !== 'Others' || otherInterestInSignoz !== '')
interestInSignoz.length > 0 &&
(!interestInSignoz.includes('Others') || otherInterestInSignoz !== '')
) {
setIsNextDisabled(false);
} else {
@ -56,6 +59,14 @@ export function AboutSigNozQuestions({
}
}, [interestInSignoz, otherInterestInSignoz, discoverSignoz]);
const handleInterestChange = (option: string, checked: boolean): void => {
if (checked) {
setInterestInSignoz((prev) => [...prev, option]);
} else {
setInterestInSignoz((prev) => prev.filter((item) => item !== option));
}
};
const handleOnNext = (): void => {
setSignozDetails({
discoverSignoz,
@ -108,50 +119,45 @@ export function AboutSigNozQuestions({
<div className="form-group">
<div className="question">What got you interested in SigNoz?</div>
<div className="two-column-grid">
<div className="checkbox-grid">
{Object.keys(interestedInOptions).map((option: string) => (
<Button
key={option}
type="primary"
className={`onboarding-questionaire-button ${
interestInSignoz === option ? 'active' : ''
}`}
onClick={(): void => setInterestInSignoz(option)}
>
{interestedInOptions[option]}
{interestInSignoz === option && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
<div key={option} className="checkbox-item">
<Checkbox
checked={interestInSignoz.includes(option)}
onChange={(e): void => handleInterestChange(option, e.target.checked)}
>
{interestedInOptions[option]}
</Checkbox>
</div>
))}
{interestInSignoz === 'Others' ? (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify your interest"
value={otherInterestInSignoz}
autoFocus
addonAfter={
otherInterestInSignoz !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
<div className="checkbox-item">
<Checkbox
checked={interestInSignoz.includes('Others')}
onChange={(e): void =>
handleInterestChange('Others', e.target.checked)
}
onChange={(e): void => setOtherInterestInSignoz(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
interestInSignoz === 'Others' ? 'active' : ''
}`}
onClick={(): void => setInterestInSignoz('Others')}
>
Others
</Button>
)}
</Checkbox>
{interestInSignoz.includes('Others') && (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify your interest"
value={otherInterestInSignoz}
autoFocus
addonAfter={
otherInterestInSignoz !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
}
onChange={(e): void => setOtherInterestInSignoz(e.target.value)}
/>
)}
</div>
</div>
</div>
</div>

View File

@ -94,6 +94,7 @@
border-radius: 4px;
font-size: 14px;
padding: 12px;
font-weight: 400;
&::placeholder {
color: var(--bg-vanilla-400);
@ -290,6 +291,37 @@
gap: 10px;
}
.checkbox-grid {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}
.checkbox-item {
display: flex;
flex-direction: column;
gap: 8px;
.ant-checkbox-wrapper {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
.ant-checkbox {
.ant-checkbox-inner {
border-color: var(--bg-slate-100);
background-color: var(--bg-ink-200);
}
&.ant-checkbox-checked .ant-checkbox-inner {
background-color: var(--bg-robin-500);
border-color: var(--bg-robin-500);
}
}
}
}
.onboarding-questionaire-button,
.add-another-member-button,
.remove-team-member-button {
@ -466,6 +498,7 @@
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
font-weight: 400;
&::placeholder {
color: var(--bg-slate-400);
@ -527,6 +560,24 @@
color: var(--bg-slate-300);
}
.checkbox-item {
.ant-checkbox-wrapper {
color: var(--bg-ink-300);
.ant-checkbox {
.ant-checkbox-inner {
border-color: var(--bg-vanilla-300);
background-color: var(--bg-vanilla-100);
}
&.ant-checkbox-checked .ant-checkbox-inner {
background-color: var(--bg-robin-500);
border-color: var(--bg-robin-500);
}
}
}
}
input[type='text'] {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);

View File

@ -38,6 +38,7 @@ const observabilityTools = {
AzureAppMonitor: 'Azure App Monitor',
GCPNativeO11yTools: 'GCP-native o11y tools',
Honeycomb: 'Honeycomb',
None: 'None/Starting fresh',
};
function OrgQuestions({
@ -53,9 +54,6 @@ function OrgQuestions({
const [organisationName, setOrganisationName] = useState<string>(
orgDetails?.organisationName || '',
);
const [usesObservability, setUsesObservability] = useState<boolean | null>(
orgDetails?.usesObservability || null,
);
const [observabilityTool, setObservabilityTool] = useState<string | null>(
orgDetails?.observabilityTool || null,
);
@ -83,7 +81,7 @@ function OrgQuestions({
orgDetails.organisationName === organisationName
) {
logEvent('Org Onboarding: Answered', {
usesObservability,
usesObservability: !observabilityTool?.includes('None'),
observabilityTool,
otherTool,
usesOtel,
@ -91,7 +89,7 @@ function OrgQuestions({
onNext({
organisationName,
usesObservability,
usesObservability: !observabilityTool?.includes('None'),
observabilityTool,
otherTool,
usesOtel,
@ -114,7 +112,7 @@ function OrgQuestions({
});
logEvent('Org Onboarding: Answered', {
usesObservability,
usesObservability: !observabilityTool?.includes('None'),
observabilityTool,
otherTool,
usesOtel,
@ -122,7 +120,7 @@ function OrgQuestions({
onNext({
organisationName,
usesObservability,
usesObservability: !observabilityTool?.includes('None'),
observabilityTool,
otherTool,
usesOtel,
@ -152,16 +150,16 @@ function OrgQuestions({
};
const isValidUsesObservability = (): boolean => {
if (usesObservability === null) {
return false;
}
if (usesObservability && (!observabilityTool || observabilityTool === '')) {
if (!observabilityTool || observabilityTool === '') {
return false;
}
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
if (usesObservability && observabilityTool === 'Others' && otherTool === '') {
if (
!observabilityTool?.includes('None') &&
observabilityTool === 'Others' &&
otherTool === ''
) {
return false;
}
@ -177,13 +175,7 @@ function OrgQuestions({
setIsNextDisabled(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
organisationName,
usesObservability,
usesOtel,
observabilityTool,
otherTool,
]);
}, [organisationName, usesOtel, observabilityTool, otherTool]);
const handleOnNext = (): void => {
handleOrgNameUpdate();
@ -217,99 +209,57 @@ function OrgQuestions({
</div>
<div className="form-group">
<label className="question" htmlFor="usesObservability">
Do you currently use any observability/monitoring tool?
<label className="question" htmlFor="observabilityTool">
Which observability tool do you currently use?
</label>
<div className="two-column-grid">
<Button
type="primary"
name="usesObservability"
className={`onboarding-questionaire-button ${
usesObservability === true ? 'active' : ''
}`}
onClick={(): void => {
setUsesObservability(true);
}}
>
Yes{' '}
{usesObservability === true && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
<Button
type="primary"
className={`onboarding-questionaire-button ${
usesObservability === false ? 'active' : ''
}`}
onClick={(): void => {
setUsesObservability(false);
setObservabilityTool(null);
setOtherTool('');
}}
>
No{' '}
{usesObservability === false && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
{Object.keys(observabilityTools).map((tool) => (
<Button
key={tool}
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === tool ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool(tool)}
>
{observabilityTools[tool as keyof typeof observabilityTools]}
{observabilityTool === tool && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
))}
{observabilityTool === 'Others' ? (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify the tool"
value={otherTool || ''}
autoFocus
addonAfter={
otherTool && otherTool !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
}
onChange={(e): void => setOtherTool(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === 'Others' ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool('Others')}
>
Others
</Button>
)}
</div>
</div>
{usesObservability && (
<div className="form-group">
<label className="question" htmlFor="observabilityTool">
Which observability tool do you currently use?
</label>
<div className="two-column-grid">
{Object.keys(observabilityTools).map((tool) => (
<Button
key={tool}
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === tool ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool(tool)}
>
{observabilityTools[tool as keyof typeof observabilityTools]}
{observabilityTool === tool && (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
)}
</Button>
))}
{observabilityTool === 'Others' ? (
<Input
type="text"
className="onboarding-questionaire-other-input"
placeholder="Please specify the tool"
value={otherTool || ''}
autoFocus
addonAfter={
otherTool && otherTool !== '' ? (
<CheckCircle size={12} color={Color.BG_FOREST_500} />
) : (
''
)
}
onChange={(e): void => setOtherTool(e.target.value)}
/>
) : (
<Button
type="primary"
className={`onboarding-questionaire-button ${
observabilityTool === 'Others' ? 'active' : ''
}`}
onClick={(): void => setObservabilityTool('Others')}
>
Others
</Button>
)}
</div>
</div>
)}
<div className="form-group">
<div className="question">Do you already use OpenTelemetry?</div>
<div className="two-column-grid">

View File

@ -46,7 +46,7 @@ const INITIAL_ORG_DETAILS: OrgDetails = {
};
const INITIAL_SIGNOZ_DETAILS: SignozDetails = {
interestInSignoz: '',
interestInSignoz: [],
otherInterestInSignoz: '',
discoverSignoz: '',
};
@ -145,6 +145,9 @@ function OnboardingQuestionaire(): JSX.Element {
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
// Allow user to proceed even if API fails
setCurrentStep(4);
},
},
);
@ -174,10 +177,16 @@ function OnboardingQuestionaire(): JSX.Element {
? (orgDetails?.otherTool as string)
: (orgDetails?.observabilityTool as string),
where_did_you_discover_signoz: signozDetails?.discoverSignoz as string,
reasons_for_interest_in_signoz:
signozDetails?.interestInSignoz === 'Others'
? (signozDetails?.otherInterestInSignoz as string)
: (signozDetails?.interestInSignoz as string),
reasons_for_interest_in_signoz: signozDetails?.interestInSignoz?.includes(
'Others',
)
? ([
...(signozDetails?.interestInSignoz?.filter(
(item) => item !== 'Others',
) || []),
signozDetails?.otherInterestInSignoz,
] as string[])
: (signozDetails?.interestInSignoz as string[]),
logs_scale_per_day_in_gb: optimiseSignozDetails?.logsPerDay as number,
number_of_hosts: optimiseSignozDetails?.hostsPerDay as number,
number_of_services: optimiseSignozDetails?.services as number,

View File

@ -1,5 +1,5 @@
export interface UpdateProfileProps {
reasons_for_interest_in_signoz: string;
reasons_for_interest_in_signoz: string[];
uses_otel: boolean;
has_existing_observability_tool: boolean;
existing_observability_tool: string;