feat: feedback updates

This commit is contained in:
Yunus M 2024-10-28 17:30:11 +05:30
parent abc2ec2155
commit c49a9dac1a
14 changed files with 160 additions and 90 deletions

View File

@ -10,9 +10,12 @@ const updateOrgPreference = async (
): Promise< ): Promise<
SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse
> => { > => {
const response = await axios.put(`/org/preferences`, { const response = await axios.put(
preference_value: preferencePayload.value, `/org/preferences/${preferencePayload.preferenceID}`,
}); {
preference_value: preferencePayload.value,
},
);
return { return {
statusCode: 200, statusCode: 200,

View File

@ -22,7 +22,7 @@ interface AboutSigNozQuestionsProps {
} }
const hearAboutSignozOptions: Record<string, string> = { const hearAboutSignozOptions: Record<string, string> = {
blog: 'Blog', search: 'Google / Search',
hackerNews: 'Hacker News', hackerNews: 'Hacker News',
linkedin: 'LinkedIn', linkedin: 'LinkedIn',
twitter: 'Twitter', twitter: 'Twitter',
@ -144,7 +144,7 @@ export function AboutSigNozQuestions({
<Input <Input
type="text" type="text"
className="onboarding-questionaire-other-input" className="onboarding-questionaire-other-input"
placeholder="Please specify your interest" placeholder="Tell us how you got to know about us"
value={otherAboutSignoz} value={otherAboutSignoz}
autoFocus autoFocus
addonAfter={ addonAfter={
@ -171,9 +171,7 @@ export function AboutSigNozQuestions({
</div> </div>
<div className="form-group"> <div className="form-group">
<div className="question"> <div className="question">What got you interested in SigNoz?</div>
What are you interested in doing with SigNoz?
</div>
<div className="two-column-grid"> <div className="two-column-grid">
{Object.keys(interestedInOptions).map((option: string) => ( {Object.keys(interestedInOptions).map((option: string) => (
<Button <Button

View File

@ -171,7 +171,7 @@ function InviteTeamMembers({
return ( return (
<div className="questions-container"> <div className="questions-container">
<Typography.Title level={3} className="title"> <Typography.Title level={3} className="title">
Observability made collaborative Invite your team members
</Typography.Title> </Typography.Title>
<Typography.Paragraph className="sub-title"> <Typography.Paragraph className="sub-title">
The more your team uses SigNoz, the stronger your observability. Share The more your team uses SigNoz, the stronger your observability. Share

View File

@ -15,6 +15,9 @@
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--Greyscale-Slate-500, #161922); border: 1px solid var(--Greyscale-Slate-500, #161922);
background: var(--Ink-400, #121317); background: var(--Ink-400, #121317);
width: 100%;
max-width: 600px;
} }
.footer-container .footer-content { .footer-container .footer-content {

View File

@ -1,6 +1,6 @@
import './OnboardingFooter.styles.scss'; import './OnboardingFooter.styles.scss';
import { ArrowUpRight, Dot } from 'lucide-react'; import { Dot } from 'lucide-react';
export function OnboardingFooter(): JSX.Element { export function OnboardingFooter(): JSX.Element {
return ( return (
@ -25,30 +25,6 @@ export function OnboardingFooter(): JSX.Element {
<img src="/logos/soc2.svg" alt="SOC2" className="footer-logo" /> <img src="/logos/soc2.svg" alt="SOC2" className="footer-logo" />
<span className="footer-text">SOC2</span> <span className="footer-text">SOC2</span>
</a> </a>
<Dot size={24} color="#2C3140" />
<a
href="https://signoz.io/privacy/"
target="_blank"
className="footer-link"
rel="noreferrer"
>
{' '}
{/* Please add correct url */}
<span className="footer-text">Privacy</span> <ArrowUpRight size={14} />
</a>
<Dot size={24} color="#2C3140" />
<a
href="https://signoz.io/security/"
target="_blank"
className="footer-link"
rel="noreferrer"
>
{' '}
{/* Please add correct url */}
<span className="footer-text">Security</span> <ArrowUpRight size={14} />
</a>
</div> </div>
</section> </section>
); );

View File

@ -40,6 +40,7 @@
border-radius: 2px; border-radius: 2px;
border: 1px solid var(--Greyscale-Slate-400, #1d212d); border: 1px solid var(--Greyscale-Slate-400, #1d212d);
background: var(--Ink-300, #16181d); background: var(--Ink-300, #16181d);
box-shadow: none;
} }
.header-container .get-help-container img { .header-container .get-help-container img {

View File

@ -1,19 +1,26 @@
import './OnboardingHeader.styles.scss'; import './OnboardingHeader.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { LifeBuoy } from 'lucide-react'; import { LifeBuoy } from 'lucide-react';
export function OnboardingHeader(): JSX.Element { export function OnboardingHeader(): JSX.Element {
const handleGetHelpClick = (): void => {
if (window.Intercom) {
window.Intercom('showNewMessage', '');
}
};
return ( return (
<div className="header-container"> <div className="header-container">
<div className="logo-container"> <div className="logo-container">
<img src="/Logos/signoz-brand-logo-new.svg" alt="SigNoz" /> <img src="/Logos/signoz-brand-logo-new.svg" alt="SigNoz" />
<span className="logo-text">SigNoz</span> <span className="logo-text">SigNoz</span>
</div> </div>
<div className="get-help-container"> <Button className="get-help-container" onClick={handleGetHelpClick}>
<LifeBuoy size={12} color={Color.BG_VANILLA_400} /> <LifeBuoy size={12} color={Color.BG_VANILLA_400} />
<span className="get-help-text ">Get Help</span> <span className="get-help-text ">Get Help</span>
</div> </Button>
</div> </div>
); );
} }

View File

@ -386,3 +386,13 @@
margin-top: 12px; margin-top: 12px;
} }
} }
.onboarding-questionaire-loading-container {
width: 100%;
display: flex;
height: 100vh;
max-width: 600px;
justify-content: center;
align-items: center;
margin: 0 auto;
}

View File

@ -16,10 +16,11 @@ interface OptimiseSignozNeedsProps {
onBack: () => void; onBack: () => void;
onWillDoLater: () => void; onWillDoLater: () => void;
isUpdatingProfile: boolean; isUpdatingProfile: boolean;
isNextDisabled: boolean;
} }
const logMarks: SliderSingleProps['marks'] = { const logMarks: SliderSingleProps['marks'] = {
0: '2 GB', 0: '0 GB',
25: '25 GB', 25: '25 GB',
50: '50 GB', 50: '50 GB',
100: '100 GB', 100: '100 GB',
@ -50,15 +51,16 @@ function OptimiseSignozNeeds({
onNext, onNext,
onBack, onBack,
onWillDoLater, onWillDoLater,
isNextDisabled,
}: OptimiseSignozNeedsProps): JSX.Element { }: OptimiseSignozNeedsProps): JSX.Element {
const [logsPerDay, setLogsPerDay] = useState<number>( const [logsPerDay, setLogsPerDay] = useState<number>(
optimiseSignozDetails?.logsPerDay || 25, optimiseSignozDetails?.logsPerDay || 0,
); );
const [hostsPerDay, setHostsPerDay] = useState<number>( const [hostsPerDay, setHostsPerDay] = useState<number>(
optimiseSignozDetails?.hostsPerDay || 40, optimiseSignozDetails?.hostsPerDay || 0,
); );
const [services, setServices] = useState<number>( const [services, setServices] = useState<number>(
optimiseSignozDetails?.services || 10, optimiseSignozDetails?.services || 0,
); );
useEffect(() => { useEffect(() => {
@ -191,7 +193,7 @@ function OptimiseSignozNeeds({
type="primary" type="primary"
className="next-button" className="next-button"
onClick={handleOnNext} onClick={handleOnNext}
disabled={isUpdatingProfile} disabled={isUpdatingProfile || isNextDisabled}
> >
Next{' '} Next{' '}
{isUpdatingProfile ? ( {isUpdatingProfile ? (
@ -204,7 +206,7 @@ function OptimiseSignozNeeds({
<div className="do-later-container"> <div className="do-later-container">
<Button type="link" onClick={handleWillDoLater}> <Button type="link" onClick={handleWillDoLater}>
I&apos;ll do this later Skip for now
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -42,10 +42,10 @@ const observabilityTools = {
}; };
const o11yFamiliarityOptions: Record<string, string> = { const o11yFamiliarityOptions: Record<string, string> = {
new: "I'm completely new", beginner: 'Basic Understanding',
builtStack: "I've built a stack before", intermediate: 'Somewhat Familiar',
experienced: 'I have some experience', expert: 'Very Familiar',
dontKnow: "I don't know what it is", notFamiliar: "I'm not familiar with it",
}; };
function OrgQuestions({ function OrgQuestions({
@ -254,7 +254,7 @@ function OrgQuestions({
<div className="form-group"> <div className="form-group">
<div className="question"> <div className="question">
Are you familiar with observability (o11y)? Are you familiar with setting upobservability (o11y)?
</div> </div>
<div className="two-column-grid"> <div className="two-column-grid">
{Object.keys(o11yFamiliarityOptions).map((option: string) => ( {Object.keys(o11yFamiliarityOptions).map((option: string) => (

View File

@ -1,7 +1,10 @@
import './OnboardingQuestionaire.styles.scss'; import './OnboardingQuestionaire.styles.scss';
import { Skeleton } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface'; import { NotificationInstance } from 'antd/es/notification/interface';
import updateProfileAPI from 'api/onboarding/updateProfile'; import updateProfileAPI from 'api/onboarding/updateProfile';
import getOrgPreference from 'api/preferences/getOrgPreference';
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
import editOrg from 'api/user/editOrg'; import editOrg from 'api/user/editOrg';
import getOrgUser from 'api/user/getOrgUser'; import getOrgUser from 'api/user/getOrgUser';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
@ -10,6 +13,7 @@ import ROUTES from 'constants/routes';
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history'; import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import { Dispatch, useEffect, useState } from 'react'; import { Dispatch, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation, useQuery } from 'react-query'; import { useMutation, useQuery } from 'react-query';
@ -56,9 +60,9 @@ const INITIAL_SIGNOZ_DETAILS: SignozDetails = {
}; };
const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
logsPerDay: 25, logsPerDay: 0,
hostsPerDay: 40, hostsPerDay: 0,
services: 10, services: 0,
}; };
function OnboardingQuestionaire(): JSX.Element { function OnboardingQuestionaire(): JSX.Element {
@ -92,6 +96,44 @@ function OnboardingQuestionaire(): JSX.Element {
const dispatch = useDispatch<Dispatch<AppActions>>(); const dispatch = useDispatch<Dispatch<AppActions>>();
const [orgData, setOrgData] = useState<OrgData | null>(null); const [orgData, setOrgData] = useState<OrgData | null>(null);
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
false,
);
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }),
queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'],
});
useEffect(() => {
if (!isLoadingOrgPreferences && !isEmpty(orgPreferences?.payload?.data)) {
const preferenceId = orgPreferences?.payload?.data?.preference_id;
const preferenceValue = orgPreferences?.payload?.data?.preference_value;
if (preferenceId === 'ORG_ONBOARDING') {
setIsOnboardingComplete(preferenceValue as boolean);
}
}
}, [orgPreferences, isLoadingOrgPreferences]);
const checkFirstTimeUser = (): boolean => {
const users = orgUsers?.payload || [];
const remainingUsers = users.filter(
(user) => user.email !== 'admin@signoz.cloud',
);
return remainingUsers.length === 1;
};
useEffect(() => {
const isFirstUser = checkFirstTimeUser();
if (isOnboardingComplete || !isFirstUser) {
history.push(ROUTES.GET_STARTED);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOnboardingComplete, orgUsers]);
useEffect(() => { useEffect(() => {
if (org) { if (org) {
@ -105,6 +147,11 @@ function OnboardingQuestionaire(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [org]); }, [org]);
const isNextDisabled =
optimiseSignozDetails.logsPerDay === 0 ||
optimiseSignozDetails.hostsPerDay === 0 ||
optimiseSignozDetails.services === 0;
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const handleOrgNameUpdate = async (): Promise<void> => { const handleOrgNameUpdate = async (): Promise<void> => {
@ -164,9 +211,7 @@ function OnboardingQuestionaire(): JSX.Element {
const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation( const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation(
updateProfileAPI, updateProfileAPI,
{ {
onSuccess: (data) => { onSuccess: () => {
console.log('data', data);
setCurrentStep(4); setCurrentStep(4);
}, },
onError: (error) => { onError: (error) => {
@ -175,6 +220,15 @@ function OnboardingQuestionaire(): JSX.Element {
}, },
); );
const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, {
onSuccess: () => {
setIsOnboardingComplete(true);
},
onError: (error) => {
showErrorNotification(notifications, error as AxiosError);
},
});
const handleUpdateProfile = (): void => { const handleUpdateProfile = (): void => {
updateProfile({ updateProfile({
familiarity_with_observability: orgDetails?.familiarity as string, familiarity_with_observability: orgDetails?.familiarity as string,
@ -200,7 +254,10 @@ function OnboardingQuestionaire(): JSX.Element {
}; };
const handleOnboardingComplete = (): void => { const handleOnboardingComplete = (): void => {
history.push(ROUTES.GET_STARTED); updateOrgPreference({
preferenceID: 'ORG_ONBOARDING',
value: true,
});
}; };
return ( return (
@ -210,42 +267,53 @@ function OnboardingQuestionaire(): JSX.Element {
</div> </div>
<div className="onboarding-questionaire-content"> <div className="onboarding-questionaire-content">
{currentStep === 1 && ( {(isLoadingOrgPreferences || isLoadingOrgUsers) && (
<OrgQuestions <div className="onboarding-questionaire-loading-container">
isLoading={isLoading} <Skeleton />
orgDetails={orgDetails} </div>
setOrgDetails={setOrgDetails}
onNext={handleOrgDetailsUpdate}
/>
)} )}
{currentStep === 2 && ( {!isLoadingOrgPreferences && !isLoadingOrgUsers && (
<AboutSigNozQuestions <>
signozDetails={signozDetails} {currentStep === 1 && (
setSignozDetails={setSignozDetails} <OrgQuestions
onBack={(): void => setCurrentStep(1)} isLoading={isLoading}
onNext={(): void => setCurrentStep(3)} orgDetails={orgDetails}
/> setOrgDetails={setOrgDetails}
)} onNext={handleOrgDetailsUpdate}
/>
)}
{currentStep === 3 && ( {currentStep === 2 && (
<OptimiseSignozNeeds <AboutSigNozQuestions
isUpdatingProfile={isUpdatingProfile} signozDetails={signozDetails}
optimiseSignozDetails={optimiseSignozDetails} setSignozDetails={setSignozDetails}
setOptimiseSignozDetails={setOptimiseSignozDetails} onBack={(): void => setCurrentStep(1)}
onBack={(): void => setCurrentStep(2)} onNext={(): void => setCurrentStep(3)}
onNext={handleUpdateProfile} />
onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet )}
/>
)}
{currentStep === 4 && ( {currentStep === 3 && (
<InviteTeamMembers <OptimiseSignozNeeds
teamMembers={teamMembers} isNextDisabled={isNextDisabled}
setTeamMembers={setTeamMembers} isUpdatingProfile={isUpdatingProfile}
onBack={(): void => setCurrentStep(3)} optimiseSignozDetails={optimiseSignozDetails}
onNext={handleOnboardingComplete} setOptimiseSignozDetails={setOptimiseSignozDetails}
/> onBack={(): void => setCurrentStep(2)}
onNext={handleUpdateProfile}
onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet
/>
)}
{currentStep === 4 && (
<InviteTeamMembers
teamMembers={teamMembers}
setTeamMembers={setTeamMembers}
onBack={(): void => setCurrentStep(3)}
onNext={handleOnboardingComplete}
/>
)}
</>
)} )}
</div> </div>

View File

@ -113,7 +113,9 @@ function SideNav({
if (!isOnboardingEnabled || !isCloudUser()) { if (!isOnboardingEnabled || !isCloudUser()) {
let items = [...menuItems]; let items = [...menuItems];
items = items.filter((item) => item.key !== ROUTES.GET_STARTED); items = items.filter(
(item) => item.key !== ROUTES.GET_STARTED && item.key !== ROUTES.ONBOARDING,
);
setMenuItems(items); setMenuItems(items);
} }

View File

@ -261,7 +261,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
values, values,
async (): Promise<void> => { async (): Promise<void> => {
if (isOnboardingEnabled && isCloudUser()) { if (isOnboardingEnabled && isCloudUser()) {
history.push(ROUTES.GET_STARTED); history.push(ROUTES.ONBOARDING);
} else { } else {
history.push(ROUTES.APPLICATION); history.push(ROUTES.APPLICATION);
} }

View File

@ -19,12 +19,12 @@ export interface GetAllUserPreferencesResponseProps {
} }
export interface UpdateOrgPreferenceProps { export interface UpdateOrgPreferenceProps {
key: string; preferenceID: string;
value: unknown; value: unknown;
} }
export interface UpdateUserPreferenceProps { export interface UpdateUserPreferenceProps {
key: string; preferenceID: string;
value: unknown; value: unknown;
} }