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<
SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse
> => {
const response = await axios.put(`/org/preferences`, {
preference_value: preferencePayload.value,
});
const response = await axios.put(
`/org/preferences/${preferencePayload.preferenceID}`,
{
preference_value: preferencePayload.value,
},
);
return {
statusCode: 200,

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import './OnboardingFooter.styles.scss';
import { ArrowUpRight, Dot } from 'lucide-react';
import { Dot } from 'lucide-react';
export function OnboardingFooter(): JSX.Element {
return (
@ -25,30 +25,6 @@ export function OnboardingFooter(): JSX.Element {
<img src="/logos/soc2.svg" alt="SOC2" className="footer-logo" />
<span className="footer-text">SOC2</span>
</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>
</section>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,10 @@
import './OnboardingQuestionaire.styles.scss';
import { Skeleton } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
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 getOrgUser from 'api/user/getOrgUser';
import { AxiosError } from 'axios';
@ -10,6 +13,7 @@ import ROUTES from 'constants/routes';
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import { Dispatch, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQuery } from 'react-query';
@ -56,9 +60,9 @@ const INITIAL_SIGNOZ_DETAILS: SignozDetails = {
};
const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
logsPerDay: 25,
hostsPerDay: 40,
services: 10,
logsPerDay: 0,
hostsPerDay: 0,
services: 0,
};
function OnboardingQuestionaire(): JSX.Element {
@ -92,6 +96,44 @@ function OnboardingQuestionaire(): JSX.Element {
const dispatch = useDispatch<Dispatch<AppActions>>();
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(() => {
if (org) {
@ -105,6 +147,11 @@ function OnboardingQuestionaire(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [org]);
const isNextDisabled =
optimiseSignozDetails.logsPerDay === 0 ||
optimiseSignozDetails.hostsPerDay === 0 ||
optimiseSignozDetails.services === 0;
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleOrgNameUpdate = async (): Promise<void> => {
@ -164,9 +211,7 @@ function OnboardingQuestionaire(): JSX.Element {
const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation(
updateProfileAPI,
{
onSuccess: (data) => {
console.log('data', data);
onSuccess: () => {
setCurrentStep(4);
},
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 => {
updateProfile({
familiarity_with_observability: orgDetails?.familiarity as string,
@ -200,7 +254,10 @@ function OnboardingQuestionaire(): JSX.Element {
};
const handleOnboardingComplete = (): void => {
history.push(ROUTES.GET_STARTED);
updateOrgPreference({
preferenceID: 'ORG_ONBOARDING',
value: true,
});
};
return (
@ -210,42 +267,53 @@ function OnboardingQuestionaire(): JSX.Element {
</div>
<div className="onboarding-questionaire-content">
{currentStep === 1 && (
<OrgQuestions
isLoading={isLoading}
orgDetails={orgDetails}
setOrgDetails={setOrgDetails}
onNext={handleOrgDetailsUpdate}
/>
{(isLoadingOrgPreferences || isLoadingOrgUsers) && (
<div className="onboarding-questionaire-loading-container">
<Skeleton />
</div>
)}
{currentStep === 2 && (
<AboutSigNozQuestions
signozDetails={signozDetails}
setSignozDetails={setSignozDetails}
onBack={(): void => setCurrentStep(1)}
onNext={(): void => setCurrentStep(3)}
/>
)}
{!isLoadingOrgPreferences && !isLoadingOrgUsers && (
<>
{currentStep === 1 && (
<OrgQuestions
isLoading={isLoading}
orgDetails={orgDetails}
setOrgDetails={setOrgDetails}
onNext={handleOrgDetailsUpdate}
/>
)}
{currentStep === 3 && (
<OptimiseSignozNeeds
isUpdatingProfile={isUpdatingProfile}
optimiseSignozDetails={optimiseSignozDetails}
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 === 2 && (
<AboutSigNozQuestions
signozDetails={signozDetails}
setSignozDetails={setSignozDetails}
onBack={(): void => setCurrentStep(1)}
onNext={(): void => setCurrentStep(3)}
/>
)}
{currentStep === 4 && (
<InviteTeamMembers
teamMembers={teamMembers}
setTeamMembers={setTeamMembers}
onBack={(): void => setCurrentStep(3)}
onNext={handleOnboardingComplete}
/>
{currentStep === 3 && (
<OptimiseSignozNeeds
isNextDisabled={isNextDisabled}
isUpdatingProfile={isUpdatingProfile}
optimiseSignozDetails={optimiseSignozDetails}
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>

View File

@ -113,7 +113,9 @@ function SideNav({
if (!isOnboardingEnabled || !isCloudUser()) {
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);
}

View File

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

View File

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