mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 15:39:06 +08:00
feat: feedback updates
This commit is contained in:
parent
abc2ec2155
commit
c49a9dac1a
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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'll do this later
|
||||
Skip for now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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) => (
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user