forked from github/plane
fix: merge conflicts resolved
This commit is contained in:
commit
b86c6c906a
@ -401,8 +401,8 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign up",
|
||||||
medium="MAGIC_LINK",
|
medium="Magic link",
|
||||||
first_time=True,
|
first_time=True,
|
||||||
)
|
)
|
||||||
key, token, current_attempt = generate_magic_token(email=email)
|
key, token, current_attempt = generate_magic_token(email=email)
|
||||||
@ -438,8 +438,8 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign in",
|
||||||
medium="MAGIC_LINK",
|
medium="Magic link",
|
||||||
first_time=False,
|
first_time=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -468,8 +468,8 @@ class EmailCheckEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign in",
|
||||||
medium="EMAIL",
|
medium="Email",
|
||||||
first_time=False,
|
first_time=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -274,8 +274,8 @@ class SignInEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign in",
|
||||||
medium="EMAIL",
|
medium="Email",
|
||||||
first_time=False,
|
first_time=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -349,8 +349,8 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign in",
|
||||||
medium="MAGIC_LINK",
|
medium="Magic link",
|
||||||
first_time=False,
|
first_time=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign in",
|
||||||
medium=medium.upper(),
|
medium=medium.upper(),
|
||||||
first_time=False,
|
first_time=False,
|
||||||
)
|
)
|
||||||
@ -427,7 +427,7 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
email=email,
|
email=email,
|
||||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||||
ip=request.META.get("REMOTE_ADDR"),
|
ip=request.META.get("REMOTE_ADDR"),
|
||||||
event_name="SIGN_IN",
|
event_name="Sign up",
|
||||||
medium=medium.upper(),
|
medium=medium.upper(),
|
||||||
first_time=True,
|
first_time=True,
|
||||||
)
|
)
|
||||||
|
@ -4,12 +4,14 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { AuthService } from "services/auth.service";
|
import { AuthService } from "services/auth.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// icons
|
// icons
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
import { PASSWORD_CREATE_SELECTED, PASSWORD_CREATE_SKIPPED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -34,6 +36,8 @@ export const SignInOptionalSetPasswordForm: React.FC<Props> = (props) => {
|
|||||||
// states
|
// states
|
||||||
const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false);
|
const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// form info
|
// form info
|
||||||
@ -63,21 +67,34 @@ export const SignInOptionalSetPasswordForm: React.FC<Props> = (props) => {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Password created successfully.",
|
message: "Password created successfully.",
|
||||||
});
|
});
|
||||||
|
captureEvent(PASSWORD_CREATE_SELECTED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: false,
|
||||||
|
});
|
||||||
await handleSignInRedirection();
|
await handleSignInRedirection();
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(PASSWORD_CREATE_SELECTED, {
|
||||||
|
state: "FAILED",
|
||||||
|
first_time: false,
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoToWorkspace = async () => {
|
const handleGoToWorkspace = async () => {
|
||||||
setIsGoingToWorkspace(true);
|
setIsGoingToWorkspace(true);
|
||||||
|
await handleSignInRedirection().finally(() => {
|
||||||
await handleSignInRedirection().finally(() => setIsGoingToWorkspace(false));
|
captureEvent(PASSWORD_CREATE_SKIPPED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: false,
|
||||||
|
});
|
||||||
|
setIsGoingToWorkspace(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,7 +7,7 @@ import { Eye, EyeOff, XCircle } from "lucide-react";
|
|||||||
import { AuthService } from "services/auth.service";
|
import { AuthService } from "services/auth.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { useApplication } from "hooks/store";
|
import { useApplication, useEventTracker } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { ESignInSteps, ForgotPasswordPopover } from "components/account";
|
import { ESignInSteps, ForgotPasswordPopover } from "components/account";
|
||||||
// ui
|
// ui
|
||||||
@ -16,6 +16,8 @@ import { Button, Input } from "@plane/ui";
|
|||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IPasswordSignInData } from "@plane/types";
|
import { IPasswordSignInData } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { FORGOT_PASSWORD, SIGN_IN_WITH_PASSWORD } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -46,6 +48,7 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// derived values
|
// derived values
|
||||||
const isSmtpConfigured = envConfig?.is_smtp_configured;
|
const isSmtpConfigured = envConfig?.is_smtp_configured;
|
||||||
// form info
|
// form info
|
||||||
@ -72,7 +75,13 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
await authService
|
await authService
|
||||||
.passwordSignIn(payload)
|
.passwordSignIn(payload)
|
||||||
.then(async () => await onSubmit())
|
.then(async () => {
|
||||||
|
captureEvent(SIGN_IN_WITH_PASSWORD, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: false,
|
||||||
|
});
|
||||||
|
await onSubmit();
|
||||||
|
})
|
||||||
.catch((err) =>
|
.catch((err) =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -182,9 +191,10 @@ export const SignInPasswordForm: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="w-full text-right mt-2 pb-3">
|
<div className="mt-2 w-full pb-3 text-right">
|
||||||
{isSmtpConfigured ? (
|
{isSmtpConfigured ? (
|
||||||
<Link
|
<Link
|
||||||
|
onClick={() => captureEvent(FORGOT_PASSWORD)}
|
||||||
href={`/accounts/forgot-password?email=${email}`}
|
href={`/accounts/forgot-password?email=${email}`}
|
||||||
className="text-xs font-medium text-custom-primary-100"
|
className="text-xs font-medium text-custom-primary-100"
|
||||||
>
|
>
|
||||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication } from "hooks/store";
|
import { useApplication, useEventTracker } from "hooks/store";
|
||||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||||
// components
|
// components
|
||||||
import { LatestFeatureBlock } from "components/common";
|
import { LatestFeatureBlock } from "components/common";
|
||||||
@ -13,6 +13,8 @@ import {
|
|||||||
OAuthOptions,
|
OAuthOptions,
|
||||||
SignInOptionalSetPasswordForm,
|
SignInOptionalSetPasswordForm,
|
||||||
} from "components/account";
|
} from "components/account";
|
||||||
|
// constants
|
||||||
|
import { NAVIGATE_TO_SIGNUP } from "constants/event-tracker";
|
||||||
|
|
||||||
export enum ESignInSteps {
|
export enum ESignInSteps {
|
||||||
EMAIL = "EMAIL",
|
EMAIL = "EMAIL",
|
||||||
@ -32,6 +34,7 @@ export const SignInRoot = observer(() => {
|
|||||||
const {
|
const {
|
||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// derived values
|
// derived values
|
||||||
const isSmtpConfigured = envConfig?.is_smtp_configured;
|
const isSmtpConfigured = envConfig?.is_smtp_configured;
|
||||||
|
|
||||||
@ -110,7 +113,11 @@ export const SignInRoot = observer(() => {
|
|||||||
<OAuthOptions handleSignInRedirection={handleRedirection} type="sign_in" />
|
<OAuthOptions handleSignInRedirection={handleRedirection} type="sign_in" />
|
||||||
<p className="text-xs text-onboarding-text-300 text-center mt-6">
|
<p className="text-xs text-onboarding-text-300 text-center mt-6">
|
||||||
Don{"'"}t have an account?{" "}
|
Don{"'"}t have an account?{" "}
|
||||||
<Link href="/accounts/sign-up" className="text-custom-primary-100 font-medium underline">
|
<Link
|
||||||
|
href="/accounts/sign-up"
|
||||||
|
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
||||||
|
className="text-custom-primary-100 font-medium underline"
|
||||||
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
@ -7,12 +7,15 @@ import { UserService } from "services/user.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useTimer from "hooks/use-timer";
|
import useTimer from "hooks/use-timer";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { CODE_VERIFIED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -41,6 +44,8 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
|
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// timer
|
// timer
|
||||||
const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30);
|
const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30);
|
||||||
// form info
|
// form info
|
||||||
@ -69,17 +74,22 @@ export const SignInUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
await authService
|
await authService
|
||||||
.magicSignIn(payload)
|
.magicSignIn(payload)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
captureEvent(CODE_VERIFIED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
const currentUser = await userService.currentUser();
|
const currentUser = await userService.currentUser();
|
||||||
|
|
||||||
await onSubmit(currentUser.is_password_autoset);
|
await onSubmit(currentUser.is_password_autoset);
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(CODE_VERIFIED, {
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSendNewCode = async (formData: TUniqueCodeFormValues) => {
|
const handleSendNewCode = async (formData: TUniqueCodeFormValues) => {
|
||||||
|
@ -4,12 +4,14 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { AuthService } from "services/auth.service";
|
import { AuthService } from "services/auth.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// constants
|
// constants
|
||||||
import { ESignUpSteps } from "components/account";
|
import { ESignUpSteps } from "components/account";
|
||||||
|
import { PASSWORD_CREATE_SELECTED, PASSWORD_CREATE_SKIPPED, SETUP_PASSWORD } from "constants/event-tracker";
|
||||||
// icons
|
// icons
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
|
||||||
@ -37,6 +39,8 @@ export const SignUpOptionalSetPasswordForm: React.FC<Props> = (props) => {
|
|||||||
// states
|
// states
|
||||||
const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false);
|
const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// form info
|
// form info
|
||||||
@ -66,21 +70,34 @@ export const SignUpOptionalSetPasswordForm: React.FC<Props> = (props) => {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Password created successfully.",
|
message: "Password created successfully.",
|
||||||
});
|
});
|
||||||
|
captureEvent(SETUP_PASSWORD, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: true,
|
||||||
|
});
|
||||||
await handleSignInRedirection();
|
await handleSignInRedirection();
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(SETUP_PASSWORD, {
|
||||||
|
state: "FAILED",
|
||||||
|
first_time: true,
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoToWorkspace = async () => {
|
const handleGoToWorkspace = async () => {
|
||||||
setIsGoingToWorkspace(true);
|
setIsGoingToWorkspace(true);
|
||||||
|
await handleSignInRedirection().finally(() => {
|
||||||
await handleSignInRedirection().finally(() => setIsGoingToWorkspace(false));
|
captureEvent(PASSWORD_CREATE_SKIPPED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: true,
|
||||||
|
});
|
||||||
|
setIsGoingToWorkspace(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication } from "hooks/store";
|
import { useApplication, useEventTracker } from "hooks/store";
|
||||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
@ -12,6 +12,8 @@ import {
|
|||||||
SignUpUniqueCodeForm,
|
SignUpUniqueCodeForm,
|
||||||
} from "components/account";
|
} from "components/account";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
// constants
|
||||||
|
import { NAVIGATE_TO_SIGNIN } from "constants/event-tracker";
|
||||||
|
|
||||||
export enum ESignUpSteps {
|
export enum ESignUpSteps {
|
||||||
EMAIL = "EMAIL",
|
EMAIL = "EMAIL",
|
||||||
@ -32,6 +34,7 @@ export const SignUpRoot = observer(() => {
|
|||||||
const {
|
const {
|
||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
// step 1 submit handler- email verification
|
// step 1 submit handler- email verification
|
||||||
const handleEmailVerification = () => setSignInStep(ESignUpSteps.UNIQUE_CODE);
|
const handleEmailVerification = () => setSignInStep(ESignUpSteps.UNIQUE_CODE);
|
||||||
@ -86,7 +89,11 @@ export const SignUpRoot = observer(() => {
|
|||||||
<OAuthOptions handleSignInRedirection={handleRedirection} type="sign_up" />
|
<OAuthOptions handleSignInRedirection={handleRedirection} type="sign_up" />
|
||||||
<p className="text-xs text-onboarding-text-300 text-center mt-6">
|
<p className="text-xs text-onboarding-text-300 text-center mt-6">
|
||||||
Already using Plane?{" "}
|
Already using Plane?{" "}
|
||||||
<Link href="/" className="text-custom-primary-100 font-medium underline">
|
<Link
|
||||||
|
href="/"
|
||||||
|
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
|
||||||
|
className="text-custom-primary-100 font-medium underline"
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
@ -8,12 +8,15 @@ import { UserService } from "services/user.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useTimer from "hooks/use-timer";
|
import useTimer from "hooks/use-timer";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { CODE_VERIFIED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -39,6 +42,8 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
const { email, handleEmailClear, onSubmit } = props;
|
const { email, handleEmailClear, onSubmit } = props;
|
||||||
// states
|
// states
|
||||||
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
|
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// timer
|
// timer
|
||||||
@ -69,17 +74,22 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
await authService
|
await authService
|
||||||
.magicSignIn(payload)
|
.magicSignIn(payload)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
captureEvent(CODE_VERIFIED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
const currentUser = await userService.currentUser();
|
const currentUser = await userService.currentUser();
|
||||||
|
|
||||||
await onSubmit(currentUser.is_password_autoset);
|
await onSubmit(currentUser.is_password_autoset);
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(CODE_VERIFIED, {
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSendNewCode = async (formData: TUniqueCodeFormValues) => {
|
const handleSendNewCode = async (formData: TUniqueCodeFormValues) => {
|
||||||
@ -96,7 +106,6 @@ export const SignUpUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "A new unique code has been sent to your email.",
|
message: "A new unique code has been sent to your email.",
|
||||||
});
|
});
|
||||||
|
|
||||||
reset({
|
reset({
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
token: "",
|
token: "",
|
||||||
|
@ -16,6 +16,7 @@ import { copyTextToClipboard } from "helpers/string.helper";
|
|||||||
// constants
|
// constants
|
||||||
import { CYCLE_STATUS } from "constants/cycle";
|
import { CYCLE_STATUS } from "constants/cycle";
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED } from "constants/event-tracker";
|
||||||
//.types
|
//.types
|
||||||
import { TCycleGroups } from "@plane/types";
|
import { TCycleGroups } from "@plane/types";
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store
|
// store
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -90,39 +91,55 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).catch(() => {
|
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(CYCLE_FAVORITED, {
|
||||||
title: "Error!",
|
cycle_id: cycleId,
|
||||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).catch(() => {
|
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(CYCLE_UNFAVORITED, {
|
||||||
title: "Error!",
|
cycle_id: cycleId,
|
||||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setTrackElement("Cycles page board layout");
|
setTrackElement("Cycles page grid layout");
|
||||||
setUpdateModal(true);
|
setUpdateModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleDeleteCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setTrackElement("Cycles page board layout");
|
setTrackElement("Cycles page grid layout");
|
||||||
setDeleteModal(true);
|
setDeleteModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import { CYCLE_STATUS } from "constants/cycle";
|
|||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
// types
|
// types
|
||||||
import { TCycleGroups } from "@plane/types";
|
import { TCycleGroups } from "@plane/types";
|
||||||
|
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED } from "constants/event-tracker";
|
||||||
|
|
||||||
type TCyclesListItem = {
|
type TCyclesListItem = {
|
||||||
cycleId: string;
|
cycleId: string;
|
||||||
@ -37,7 +38,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -63,26 +64,42 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).catch(() => {
|
addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(CYCLE_FAVORITED, {
|
||||||
title: "Error!",
|
cycle_id: cycleId,
|
||||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
element: "List layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleRemoveFromFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).catch(() => {
|
removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(CYCLE_UNFAVORITED, {
|
||||||
title: "Error!",
|
cycle_id: cycleId,
|
||||||
message: "Couldn't add the cycle to favorites. Please try again.",
|
element: "List layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the cycle to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleEditCycle = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -159,9 +176,9 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycleDetails.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycleDetails.id}`}>
|
||||||
<div className="group flex flex-col md:flex-row w-full items-center justify-between gap-5 border-b border-custom-border-100 bg-custom-background-100 px-5 py-6 text-sm hover:bg-custom-background-90">
|
<div className="group flex w-full flex-col items-center justify-between gap-5 border-b border-custom-border-100 bg-custom-background-100 px-5 py-6 text-sm hover:bg-custom-background-90 md:flex-row">
|
||||||
<div className="relative w-full flex items-center justify-between gap-3 overflow-hidden">
|
<div className="relative flex w-full items-center justify-between gap-3 overflow-hidden">
|
||||||
<div className="relative w-full flex items-center gap-3 overflow-hidden">
|
<div className="relative flex w-full items-center gap-3 overflow-hidden">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CircularProgressIndicator size={38} percentage={progress}>
|
<CircularProgressIndicator size={38} percentage={progress}>
|
||||||
{isCompleted ? (
|
{isCompleted ? (
|
||||||
@ -181,20 +198,20 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
<div className="relative flex items-center gap-2.5 overflow-hidden">
|
<div className="relative flex items-center gap-2.5 overflow-hidden">
|
||||||
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3.5 w-3.5 flex-shrink-0" />
|
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||||
<Tooltip tooltipContent={cycleDetails.name} position="top">
|
<Tooltip tooltipContent={cycleDetails.name} position="top">
|
||||||
<span className="truncate line-clamp-1 inline-block overflow-hidden text-base font-medium">
|
<span className="line-clamp-1 inline-block overflow-hidden truncate text-base font-medium">
|
||||||
{cycleDetails.name}
|
{cycleDetails.name}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={openCycleOverview} className="flex-shrink-0 z-10 invisible group-hover:visible">
|
<button onClick={openCycleOverview} className="invisible z-10 flex-shrink-0 group-hover:visible">
|
||||||
<Info className="h-4 w-4 text-custom-text-400" />
|
<Info className="h-4 w-4 text-custom-text-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentCycle && (
|
{currentCycle && (
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 relative flex h-6 w-20 items-center justify-center rounded-sm text-center text-xs"
|
className="relative flex h-6 w-20 flex-shrink-0 items-center justify-center rounded-sm text-center text-xs"
|
||||||
style={{
|
style={{
|
||||||
color: currentCycle.color,
|
color: currentCycle.color,
|
||||||
backgroundColor: `${currentCycle.color}20`,
|
backgroundColor: `${currentCycle.color}20`,
|
||||||
@ -206,12 +223,12 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 relative overflow-hidden flex w-full items-center justify-between md:justify-end gap-2.5 md:w-auto md:flex-shrink-0 ">
|
<div className="relative flex w-full flex-shrink-0 items-center justify-between gap-2.5 overflow-hidden md:w-auto md:flex-shrink-0 md:justify-end ">
|
||||||
<div className="text-xs text-custom-text-300">
|
<div className="text-xs text-custom-text-300">
|
||||||
{renderDate && `${renderFormattedDate(startDate) ?? `_ _`} - ${renderFormattedDate(endDate) ?? `_ _`}`}
|
{renderDate && `${renderFormattedDate(startDate) ?? `_ _`} - ${renderFormattedDate(endDate) ?? `_ _`}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-shrink-0 relative flex items-center gap-3">
|
<div className="relative flex flex-shrink-0 items-center gap-3">
|
||||||
<Tooltip tooltipContent={`${cycleDetails.assignees.length} Members`}>
|
<Tooltip tooltipContent={`${cycleDetails.assignees.length} Members`}>
|
||||||
<div className="flex w-10 cursor-default items-center justify-center">
|
<div className="flex w-10 cursor-default items-center justify-center">
|
||||||
{cycleDetails.assignees.length > 0 ? (
|
{cycleDetails.assignees.length > 0 ? (
|
||||||
|
@ -10,6 +10,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { CYCLE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
interface ICycleDelete {
|
interface ICycleDelete {
|
||||||
cycle: ICycle;
|
cycle: ICycle;
|
||||||
@ -45,13 +47,13 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||||||
message: "Cycle deleted successfully.",
|
message: "Cycle deleted successfully.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureCycleEvent({
|
||||||
eventName: "Cycle deleted",
|
eventName: CYCLE_DELETED,
|
||||||
payload: { ...cycle, state: "SUCCESS" },
|
payload: { ...cycle, state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
captureCycleEvent({
|
captureCycleEvent({
|
||||||
eventName: "Cycle deleted",
|
eventName: CYCLE_DELETED,
|
||||||
payload: { ...cycle, state: "FAILED" },
|
payload: { ...cycle, state: "FAILED" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
|||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
|
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -29,7 +29,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
||||||
// form data
|
// form data
|
||||||
const {
|
const {
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting, dirtyFields },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
watch,
|
watch,
|
||||||
@ -61,7 +61,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
maxDate?.setDate(maxDate.getDate() - 1);
|
maxDate?.setDate(maxDate.getDate() - 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
<form onSubmit={handleSubmit((formData)=>handleFormSubmit(formData,dirtyFields))}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
{!status && (
|
{!status && (
|
||||||
|
@ -10,6 +10,8 @@ import useLocalStorage from "hooks/use-local-storage";
|
|||||||
import { CycleForm } from "components/cycles";
|
import { CycleForm } from "components/cycles";
|
||||||
// types
|
// types
|
||||||
import type { CycleDateCheckData, ICycle, TCycleView } from "@plane/types";
|
import type { CycleDateCheckData, ICycle, TCycleView } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { CYCLE_CREATED, CYCLE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type CycleModalProps = {
|
type CycleModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -47,7 +49,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
message: "Cycle created successfully.",
|
message: "Cycle created successfully.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureCycleEvent({
|
||||||
eventName: "Cycle created",
|
eventName: CYCLE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: { ...res, state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -58,18 +60,23 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
message: err.detail ?? "Error in creating cycle. Please try again.",
|
message: err.detail ?? "Error in creating cycle. Please try again.",
|
||||||
});
|
});
|
||||||
captureCycleEvent({
|
captureCycleEvent({
|
||||||
eventName: "Cycle created",
|
eventName: CYCLE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { ...payload, state: "FAILED" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>) => {
|
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>, dirtyFields: any) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const selectedProjectId = payload.project ?? projectId.toString();
|
const selectedProjectId = payload.project ?? projectId.toString();
|
||||||
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
||||||
.then(() => {
|
.then((res) => {
|
||||||
|
const changed_properties = Object.keys(dirtyFields);
|
||||||
|
captureCycleEvent({
|
||||||
|
eventName: CYCLE_UPDATED,
|
||||||
|
payload: { ...res, changed_properties: changed_properties, state: "SUCCESS" },
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -77,6 +84,10 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
captureCycleEvent({
|
||||||
|
eventName: CYCLE_UPDATED,
|
||||||
|
payload: { ...payload, state: "FAILED" },
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -95,7 +106,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
return status;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (formData: Partial<ICycle>) => {
|
const handleFormSubmit = async (formData: Partial<ICycle>, dirtyFields: any) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload: Partial<ICycle> = {
|
const payload: Partial<ICycle> = {
|
||||||
@ -119,7 +130,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
if (data) await handleUpdateCycle(data.id, payload);
|
if (data) await handleUpdateCycle(data.id, payload, dirtyFields);
|
||||||
else {
|
else {
|
||||||
await handleCreateCycle(payload).then(() => {
|
await handleCreateCycle(payload).then(() => {
|
||||||
setCycleTab("all");
|
setCycleTab("all");
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { CYCLE_UPDATED } from "constants/event-tracker";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_STATUS } from "constants/cycle";
|
import { CYCLE_STATUS } from "constants/cycle";
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, peekCycle } = router.query;
|
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureCycleEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -83,10 +84,32 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitChanges = (data: Partial<ICycle>) => {
|
const submitChanges = (data: Partial<ICycle>, changedProperty: string) => {
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||||
|
|
||||||
updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), data);
|
updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), data)
|
||||||
|
.then((res) => {
|
||||||
|
captureCycleEvent({
|
||||||
|
eventName: CYCLE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...res,
|
||||||
|
changed_properties: [changedProperty],
|
||||||
|
element: "Right side-peek",
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.catch((_) => {
|
||||||
|
captureCycleEvent({
|
||||||
|
eventName: CYCLE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...data,
|
||||||
|
element: "Right side-peek",
|
||||||
|
state: "FAILED",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
@ -146,10 +169,13 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isDateValidForExistingCycle) {
|
if (isDateValidForExistingCycle) {
|
||||||
submitChanges({
|
submitChanges(
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
{
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
||||||
});
|
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
||||||
|
},
|
||||||
|
"start_date"
|
||||||
|
);
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -174,10 +200,13 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
submitChanges({
|
submitChanges(
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
{
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
||||||
});
|
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
||||||
|
},
|
||||||
|
"start_date"
|
||||||
|
);
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -219,10 +248,13 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isDateValidForExistingCycle) {
|
if (isDateValidForExistingCycle) {
|
||||||
submitChanges({
|
submitChanges(
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
{
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
||||||
});
|
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
||||||
|
},
|
||||||
|
"end_date"
|
||||||
|
);
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -246,10 +278,13 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
submitChanges({
|
submitChanges(
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
{
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
||||||
});
|
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
||||||
|
},
|
||||||
|
"end_date"
|
||||||
|
);
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
|
@ -2,7 +2,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { FileText, Plus } from "lucide-react";
|
import { FileText, Plus } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useProject, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button } from "@plane/ui";
|
import { Breadcrumbs, Button } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -25,6 +25,7 @@ export const PagesHeader = observer(() => {
|
|||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
|
const { setTrackElement } = useEventTracker();
|
||||||
|
|
||||||
const canUserCreatePage =
|
const canUserCreatePage =
|
||||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||||
@ -64,7 +65,15 @@ export const PagesHeader = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
{canUserCreatePage && (
|
{canUserCreatePage && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="primary" prependIcon={<Plus />} size="sm" onClick={() => toggleCreatePageModal(true)}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
prependIcon={<Plus />}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setTrackElement("Project pages page");
|
||||||
|
toggleCreatePageModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
Create Page
|
Create Page
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,13 +4,18 @@ import { useTheme } from "next-themes";
|
|||||||
// images
|
// images
|
||||||
import githubBlackImage from "/public/logos/github-black.png";
|
import githubBlackImage from "/public/logos/github-black.png";
|
||||||
import githubWhiteImage from "/public/logos/github-white.png";
|
import githubWhiteImage from "/public/logos/github-white.png";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "components/common";
|
import { BreadcrumbLink } from "components/common";
|
||||||
import { Breadcrumbs } from "@plane/ui";
|
import { Breadcrumbs } from "@plane/ui";
|
||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
|
// constants
|
||||||
|
import { CHANGELOG_REDIRECTED, GITHUB_REDIRECTED } from "constants/event-tracker";
|
||||||
|
|
||||||
export const WorkspaceDashboardHeader = () => {
|
export const WorkspaceDashboardHeader = () => {
|
||||||
// hooks
|
// hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,16 +36,26 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 px-3">
|
<div className="flex items-center gap-3 px-3">
|
||||||
<a
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
captureEvent(CHANGELOG_REDIRECTED, {
|
||||||
|
element: "navbar",
|
||||||
|
})
|
||||||
|
}
|
||||||
href="https://plane.so/changelog"
|
href="https://plane.so/changelog"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
|
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
|
||||||
>
|
>
|
||||||
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
|
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
|
||||||
<span className="text-xs hidden sm:hidden md:block font-medium">{"What's new?"}</span>
|
<span className="hidden text-xs font-medium sm:hidden md:block">{"What's new?"}</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5 "
|
onClick={() =>
|
||||||
|
captureEvent(GITHUB_REDIRECTED, {
|
||||||
|
element: "navbar",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
|
||||||
href="https://github.com/makeplane/plane"
|
href="https://github.com/makeplane/plane"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@ -51,7 +66,7 @@ export const WorkspaceDashboardHeader = () => {
|
|||||||
width={16}
|
width={16}
|
||||||
alt="GitHub Logo"
|
alt="GitHub Logo"
|
||||||
/>
|
/>
|
||||||
<span className="text-xs font-medium hidden sm:hidden md:block">Star us on GitHub</span>
|
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,7 @@ import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Trash2, XCircle
|
|||||||
// types
|
// types
|
||||||
import type { TInboxStatus, TInboxDetailedStatus } from "@plane/types";
|
import type { TInboxStatus, TInboxDetailedStatus } from "@plane/types";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { ISSUE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type TInboxIssueActionsHeader = {
|
type TInboxIssueActionsHeader = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -86,17 +87,12 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
throw new Error("Missing required parameters");
|
throw new Error("Missing required parameters");
|
||||||
await removeInboxIssue(workspaceSlug, projectId, inboxId, inboxIssueId);
|
await removeInboxIssue(workspaceSlug, projectId, inboxId, inboxIssueId);
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: {
|
payload: {
|
||||||
id: inboxIssueId,
|
id: inboxIssueId,
|
||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
element: "Inbox page",
|
element: "Inbox page",
|
||||||
},
|
}
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
router.push({
|
router.push({
|
||||||
pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`,
|
pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`,
|
||||||
@ -108,17 +104,12 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
message: "Something went wrong while deleting inbox issue. Please try again.",
|
message: "Something went wrong while deleting inbox issue. Please try again.",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: {
|
payload: {
|
||||||
id: inboxIssueId,
|
id: inboxIssueId,
|
||||||
state: "FAILED",
|
state: "FAILED",
|
||||||
element: "Inbox page",
|
element: "Inbox page",
|
||||||
},
|
},
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -18,6 +18,8 @@ import { GptAssistantPopover } from "components/core";
|
|||||||
import { Button, Input, ToggleSwitch } from "@plane/ui";
|
import { Button, Input, ToggleSwitch } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -65,7 +67,6 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@ -94,34 +95,24 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||||||
handleClose();
|
handleClose();
|
||||||
} else reset(defaultValues);
|
} else reset(defaultValues);
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: {
|
payload: {
|
||||||
...formData,
|
...formData,
|
||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
element: "Inbox page",
|
element: "Inbox page",
|
||||||
},
|
},
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
path: router.pathname,
|
path: router.pathname,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: {
|
payload: {
|
||||||
...formData,
|
...formData,
|
||||||
state: "FAILED",
|
state: "FAILED",
|
||||||
element: "Inbox page",
|
element: "Inbox page",
|
||||||
},
|
},
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
path: router.pathname,
|
path: router.pathname,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -38,7 +38,7 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
|
|||||||
title: "Attachment uploaded",
|
title: "Attachment uploaded",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue attachment added",
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "attachment",
|
changed_property: "attachment",
|
||||||
@ -47,7 +47,7 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue attachment added",
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -67,7 +67,7 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
|
|||||||
title: "Attachment removed",
|
title: "Attachment removed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue attachment deleted",
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "attachment",
|
changed_property: "attachment",
|
||||||
@ -76,7 +76,7 @@ export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue attachment deleted",
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "attachment",
|
changed_property: "attachment",
|
||||||
|
@ -16,6 +16,7 @@ import { TIssue } from "@plane/types";
|
|||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
|
import { ISSUE_UPDATED, ISSUE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
export type TIssueOperations = {
|
export type TIssueOperations = {
|
||||||
fetch: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
fetch: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
@ -102,7 +103,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: Object.keys(data).join(","),
|
changed_property: Object.keys(data).join(","),
|
||||||
@ -112,7 +113,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: Object.keys(data).join(","),
|
changed_property: Object.keys(data).join(","),
|
||||||
@ -138,7 +139,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Issue deleted successfully",
|
message: "Issue deleted successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -149,7 +150,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Issue delete failed",
|
message: "Issue delete failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -164,7 +165,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Issue added to issue successfully",
|
message: "Issue added to issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -174,7 +175,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -198,7 +199,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Cycle removed from issue successfully",
|
message: "Cycle removed from issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -208,7 +209,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue detail page" },
|
payload: { state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -232,7 +233,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Module added to issue successfully",
|
message: "Module added to issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
payload: { ...response, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -242,7 +243,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -266,7 +267,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
message: "Module removed from issue successfully",
|
message: "Module removed from issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -276,7 +277,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
|
@ -13,6 +13,8 @@ import { createIssuePayload } from "helpers/issue.helper";
|
|||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
formKey: keyof TIssue;
|
formKey: keyof TIssue;
|
||||||
@ -129,7 +131,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||||||
viewId
|
viewId
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "Calendar quick add" },
|
payload: { ...res, state: "SUCCESS", element: "Calendar quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -142,7 +144,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "Calendar quick add" },
|
payload: { ...payload, state: "FAILED", element: "Calendar quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
// hooks
|
// hooks
|
||||||
import { useGlobalView, useIssues, useLabel, useUser } from "hooks/store";
|
import { useEventTracker, useGlobalView, useIssues, useLabel, useUser } from "hooks/store";
|
||||||
//ui
|
//ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -11,6 +11,8 @@ import { AppliedFiltersList } from "components/issues";
|
|||||||
import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
// constants
|
||||||
|
import { GLOBAL_VIEW_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
globalViewId: string;
|
globalViewId: string;
|
||||||
@ -27,6 +29,7 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
|||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
const { workspaceLabels } = useLabel();
|
const { workspaceLabels } = useLabel();
|
||||||
const { globalViewMap, updateGlobalView } = useGlobalView();
|
const { globalViewMap, updateGlobalView } = useGlobalView();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceRole },
|
membership: { currentWorkspaceRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -91,6 +94,13 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
|||||||
filters: {
|
filters: {
|
||||||
...(appliedFilters ?? {}),
|
...(appliedFilters ?? {}),
|
||||||
},
|
},
|
||||||
|
}).then((res) => {
|
||||||
|
captureEvent(GLOBAL_VIEW_UPDATED, {
|
||||||
|
view_id: res.id,
|
||||||
|
applied_filters: res.filters,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Spreadsheet view",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
|||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import { IProject, TIssue } from "@plane/types";
|
import { IProject, TIssue } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
interface IInputProps {
|
interface IInputProps {
|
||||||
formKey: string;
|
formKey: string;
|
||||||
@ -111,7 +113,7 @@ export const GanttQuickAddIssueForm: React.FC<IGanttQuickAddIssueForm> = observe
|
|||||||
quickAddCallback &&
|
quickAddCallback &&
|
||||||
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
|
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "Gantt quick add" },
|
payload: { ...res, state: "SUCCESS", element: "Gantt quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -123,7 +125,7 @@ export const GanttQuickAddIssueForm: React.FC<IGanttQuickAddIssueForm> = observe
|
|||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "Gantt quick add" },
|
payload: { ...payload, state: "FAILED", element: "Gantt quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@ import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile";
|
|||||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||||
import { EIssueFilterType, TCreateModalStoreTypes } from "constants/issue";
|
import { EIssueFilterType, TCreateModalStoreTypes } from "constants/issue";
|
||||||
|
import { ISSUE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IBaseKanBanLayout {
|
export interface IBaseKanBanLayout {
|
||||||
issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues;
|
issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues;
|
||||||
@ -212,7 +213,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
setDeleteIssueModal(false);
|
setDeleteIssueModal(false);
|
||||||
setDragState({});
|
setDragState({});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: { id: dragState.draggedIssueId!, state: "FAILED", element: "Kanban layout drag & drop" },
|
payload: { id: dragState.draggedIssueId!, state: "FAILED", element: "Kanban layout drag & drop" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
const Inputs = (props: any) => {
|
const Inputs = (props: any) => {
|
||||||
const { register, setFocus, projectDetail } = props;
|
const { register, setFocus, projectDetail } = props;
|
||||||
@ -106,7 +108,7 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||||||
viewId
|
viewId
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "Kanban quick add" },
|
payload: { ...res, state: "SUCCESS", element: "Kanban quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -118,7 +120,7 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "Kanban quick add" },
|
payload: { ...payload, state: "FAILED", element: "Kanban quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|||||||
import { TIssue, IProject } from "@plane/types";
|
import { TIssue, IProject } from "@plane/types";
|
||||||
// types
|
// types
|
||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
interface IInputProps {
|
interface IInputProps {
|
||||||
formKey: string;
|
formKey: string;
|
||||||
@ -103,7 +105,7 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
|
|||||||
quickAddCallback &&
|
quickAddCallback &&
|
||||||
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
|
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId).then((res) => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "List quick add" },
|
payload: { ...res, state: "SUCCESS", element: "List quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -115,7 +117,7 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
|
|||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "List quick add" },
|
payload: { ...payload, state: "FAILED", element: "List quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types";
|
import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IIssueProperties {
|
export interface IIssueProperties {
|
||||||
issue: TIssue;
|
issue: TIssue;
|
||||||
@ -40,7 +42,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleState = (stateId: string) => {
|
const handleState = (stateId: string) => {
|
||||||
handleIssues({ ...issue, state_id: stateId }).then(() => {
|
handleIssues({ ...issue, state_id: stateId }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -54,7 +56,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handlePriority = (value: TIssuePriorities) => {
|
const handlePriority = (value: TIssuePriorities) => {
|
||||||
handleIssues({ ...issue, priority: value }).then(() => {
|
handleIssues({ ...issue, priority: value }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -68,7 +70,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleLabel = (ids: string[]) => {
|
const handleLabel = (ids: string[]) => {
|
||||||
handleIssues({ ...issue, label_ids: ids }).then(() => {
|
handleIssues({ ...issue, label_ids: ids }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -82,7 +84,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleAssignee = (ids: string[]) => {
|
const handleAssignee = (ids: string[]) => {
|
||||||
handleIssues({ ...issue, assignee_ids: ids }).then(() => {
|
handleIssues({ ...issue, assignee_ids: ids }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -96,7 +98,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleStartDate = (date: Date | null) => {
|
const handleStartDate = (date: Date | null) => {
|
||||||
handleIssues({ ...issue, start_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
|
handleIssues({ ...issue, start_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -110,7 +112,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleTargetDate = (date: Date | null) => {
|
const handleTargetDate = (date: Date | null) => {
|
||||||
handleIssues({ ...issue, target_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
|
handleIssues({ ...issue, target_date: date ? renderFormattedPayloadDate(date) : null }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
@ -124,7 +126,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const handleEstimate = (value: number | null) => {
|
const handleEstimate = (value: number | null) => {
|
||||||
handleIssues({ ...issue, estimate_point: value }).then(() => {
|
handleIssues({ ...issue, estimate_point: value }).then(() => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
payload: { ...issue, state: "SUCCESS", element: currentLayout },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
updates: {
|
updates: {
|
||||||
|
@ -21,6 +21,7 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props)
|
|||||||
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={issue.target_date}
|
value={issue.target_date}
|
||||||
|
minDate={issue.start_date ? new Date(issue.start_date) : undefined}
|
||||||
onChange={(data) => {
|
onChange={(data) => {
|
||||||
const targetDate = data ? renderFormattedPayloadDate(data) : null;
|
const targetDate = data ? renderFormattedPayloadDate(data) : null;
|
||||||
onChange(
|
onChange(
|
||||||
|
@ -21,6 +21,7 @@ export const SpreadsheetStartDateColumn: React.FC<Props> = observer((props: Prop
|
|||||||
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={issue.start_date}
|
value={issue.start_date}
|
||||||
|
maxDate={issue.target_date ? new Date(issue.target_date) : undefined}
|
||||||
onChange={(data) => {
|
onChange={(data) => {
|
||||||
const startDate = data ? renderFormattedPayloadDate(data) : null;
|
const startDate = data ? renderFormattedPayloadDate(data) : null;
|
||||||
onChange(
|
onChange(
|
||||||
|
@ -12,6 +12,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|||||||
import { createIssuePayload } from "helpers/issue.helper";
|
import { createIssuePayload } from "helpers/issue.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
formKey: keyof TIssue;
|
formKey: keyof TIssue;
|
||||||
@ -162,7 +164,7 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
|
|||||||
(await quickAddCallback(currentWorkspace.slug, currentProjectDetails.id, { ...payload } as TIssue, viewId).then(
|
(await quickAddCallback(currentWorkspace.slug, currentProjectDetails.id, { ...payload } as TIssue, viewId).then(
|
||||||
(res) => {
|
(res) => {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" },
|
payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -175,7 +177,7 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
|
|||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" },
|
payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,8 @@ import { IssueFormRoot } from "./form";
|
|||||||
import type { TIssue } from "@plane/types";
|
import type { TIssue } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType, TCreateModalStoreTypes } from "constants/issue";
|
import { EIssuesStoreType, TCreateModalStoreTypes } from "constants/issue";
|
||||||
|
import { ISSUE_CREATED, ISSUE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IssuesModalProps {
|
export interface IssuesModalProps {
|
||||||
data?: Partial<TIssue>;
|
data?: Partial<TIssue>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -157,14 +159,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...response, state: "SUCCESS" },
|
payload: { ...response, state: "SUCCESS" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
!createMore && handleClose();
|
!createMore && handleClose();
|
||||||
return response;
|
return response;
|
||||||
@ -175,14 +172,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
message: "Issue could not be created. Please try again.",
|
message: "Issue could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue created",
|
eventName: ISSUE_CREATED,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { ...payload, state: "FAILED" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -198,14 +190,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
message: "Issue updated successfully.",
|
message: "Issue updated successfully.",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS" },
|
payload: { ...response, state: "SUCCESS" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
return response;
|
return response;
|
||||||
@ -216,14 +203,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
message: "Issue could not be created. Please try again.",
|
message: "Issue could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...payload, state: "FAILED" },
|
payload: { ...payload, state: "FAILED" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import { TIssue } from "@plane/types";
|
|||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
|
import { ISSUE_UPDATED, ISSUE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
interface IIssuePeekOverview {
|
interface IIssuePeekOverview {
|
||||||
is_archived?: boolean;
|
is_archived?: boolean;
|
||||||
@ -103,7 +104,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Issue updated successfully",
|
message: "Issue updated successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: Object.keys(data).join(","),
|
changed_property: Object.keys(data).join(","),
|
||||||
@ -113,7 +114,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -135,7 +136,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Issue deleted successfully",
|
message: "Issue deleted successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -146,7 +147,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Issue delete failed",
|
message: "Issue delete failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue deleted",
|
eventName: ISSUE_DELETED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
@ -161,7 +162,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Issue added to issue successfully",
|
message: "Issue added to issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -171,7 +172,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -195,7 +196,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Cycle removed from issue successfully",
|
message: "Cycle removed from issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -210,7 +211,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Cycle remove from issue failed",
|
message: "Cycle remove from issue failed",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "cycle_id",
|
changed_property: "cycle_id",
|
||||||
@ -229,7 +230,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Module added to issue successfully",
|
message: "Module added to issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { ...response, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -239,7 +240,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -263,7 +264,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
message: "Module removed from issue successfully",
|
message: "Module removed from issue successfully",
|
||||||
});
|
});
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
@ -273,7 +274,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: ISSUE_UPDATED,
|
||||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||||
updates: {
|
updates: {
|
||||||
changed_property: "module_id",
|
changed_property: "module_id",
|
||||||
|
@ -11,6 +11,8 @@ import { Button } from "@plane/ui";
|
|||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import type { IModule } from "@plane/types";
|
import type { IModule } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { MODULE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: IModule;
|
data: IModule;
|
||||||
@ -51,7 +53,7 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: "Module deleted successfully.",
|
message: "Module deleted successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module deleted",
|
eventName: MODULE_DELETED,
|
||||||
payload: { ...data, state: "SUCCESS" },
|
payload: { ...data, state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -62,7 +64,7 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: "Module could not be deleted. Please try again.",
|
message: "Module could not be deleted. Please try again.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module deleted",
|
eventName: MODULE_DELETED,
|
||||||
payload: { ...data, state: "FAILED" },
|
payload: { ...data, state: "FAILED" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
|||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleFormSubmit: (values: Partial<IModule>) => Promise<void>;
|
handleFormSubmit: (values: Partial<IModule>, dirtyFields: any) => Promise<void>;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -36,7 +36,7 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting, dirtyFields },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
watch,
|
||||||
control,
|
control,
|
||||||
@ -53,7 +53,7 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleCreateUpdateModule = async (formData: Partial<IModule>) => {
|
const handleCreateUpdateModule = async (formData: Partial<IModule>) => {
|
||||||
await handleFormSubmit(formData);
|
await handleFormSubmit(formData, dirtyFields);
|
||||||
|
|
||||||
reset({
|
reset({
|
||||||
...defaultValues,
|
...defaultValues,
|
||||||
|
@ -9,6 +9,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { ModuleForm } from "components/modules";
|
import { ModuleForm } from "components/modules";
|
||||||
// types
|
// types
|
||||||
import type { IModule } from "@plane/types";
|
import type { IModule } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { MODULE_CREATED, MODULE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -59,7 +61,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: "Module created successfully.",
|
message: "Module created successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module created",
|
eventName: MODULE_CREATED,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: { ...res, state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -70,13 +72,13 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: err.detail ?? "Module could not be created. Please try again.",
|
message: err.detail ?? "Module could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module created",
|
eventName: MODULE_CREATED,
|
||||||
payload: { ...data, state: "FAILED" },
|
payload: { ...data, state: "FAILED" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateModule = async (payload: Partial<IModule>) => {
|
const handleUpdateModule = async (payload: Partial<IModule>, dirtyFields: any) => {
|
||||||
if (!workspaceSlug || !projectId || !data) return;
|
if (!workspaceSlug || !projectId || !data) return;
|
||||||
|
|
||||||
const selectedProjectId = payload.project ?? projectId.toString();
|
const selectedProjectId = payload.project ?? projectId.toString();
|
||||||
@ -90,8 +92,8 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: "Module updated successfully.",
|
message: "Module updated successfully.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module updated",
|
eventName: MODULE_UPDATED,
|
||||||
payload: { ...res, state: "SUCCESS" },
|
payload: { ...res, changed_properties: Object.keys(dirtyFields), state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -101,20 +103,20 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
|||||||
message: err.detail ?? "Module could not be updated. Please try again.",
|
message: err.detail ?? "Module could not be updated. Please try again.",
|
||||||
});
|
});
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: "Module updated",
|
eventName: MODULE_UPDATED,
|
||||||
payload: { ...data, state: "FAILED" },
|
payload: { ...data, state: "FAILED" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (formData: Partial<IModule>) => {
|
const handleFormSubmit = async (formData: Partial<IModule>, dirtyFields: any) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload: Partial<IModule> = {
|
const payload: Partial<IModule> = {
|
||||||
...formData,
|
...formData,
|
||||||
};
|
};
|
||||||
if (!data) await handleCreateModule(payload);
|
if (!data) await handleCreateModule(payload);
|
||||||
else await handleUpdateModule(payload);
|
else await handleUpdateModule(payload, dirtyFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -16,6 +16,7 @@ import { renderFormattedDate } from "helpers/date-time.helper";
|
|||||||
// constants
|
// constants
|
||||||
import { MODULE_STATUS } from "constants/module";
|
import { MODULE_STATUS } from "constants/module";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { MODULE_FAVORITED, MODULE_UNFAVORITED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
@ -36,7 +37,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
|
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureEvent } = useEventTracker();
|
||||||
// derived values
|
// derived values
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||||
@ -46,13 +47,21 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
|
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(MODULE_FAVORITED, {
|
||||||
title: "Error!",
|
module_id: moduleId,
|
||||||
message: "Couldn't add the module to favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the module to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleRemoveFromFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -60,13 +69,21 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
|
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(MODULE_UNFAVORITED, {
|
||||||
title: "Error!",
|
module_id: moduleId,
|
||||||
message: "Couldn't remove the module from favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't remove the module from favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -84,14 +101,14 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||||||
const handleEditModule = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleEditModule = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setTrackElement("Modules page board layout");
|
setTrackElement("Modules page grid layout");
|
||||||
setEditModal(true);
|
setEditModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteModule = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleDeleteModule = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setTrackElement("Modules page board layout");
|
setTrackElement("Modules page grid layout");
|
||||||
setDeleteModal(true);
|
setDeleteModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import { renderFormattedDate } from "helpers/date-time.helper";
|
|||||||
// constants
|
// constants
|
||||||
import { MODULE_STATUS } from "constants/module";
|
import { MODULE_STATUS } from "constants/module";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { MODULE_FAVORITED, MODULE_UNFAVORITED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
@ -36,7 +37,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
|||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
|
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites } = useModule();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureEvent } = useEventTracker();
|
||||||
// derived values
|
// derived values
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||||
@ -46,13 +47,21 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
|
addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(MODULE_FAVORITED, {
|
||||||
title: "Error!",
|
module_id: moduleId,
|
||||||
message: "Couldn't add the module to favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the module to favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleRemoveFromFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -60,13 +69,21 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).catch(() => {
|
removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||||
setToastAlert({
|
.then(() => {
|
||||||
type: "error",
|
captureEvent(MODULE_UNFAVORITED, {
|
||||||
title: "Error!",
|
module_id: moduleId,
|
||||||
message: "Couldn't remove the module from favorites. Please try again.",
|
element: "Grid layout",
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't remove the module from favorites. Please try again.",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
@ -34,6 +34,7 @@ import { ILinkDetails, IModule, ModuleLink } from "@plane/types";
|
|||||||
// constant
|
// constant
|
||||||
import { MODULE_STATUS } from "constants/module";
|
import { MODULE_STATUS } from "constants/module";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { MODULE_LINK_CREATED, MODULE_LINK_DELETED, MODULE_LINK_UPDATED, MODULE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
const defaultValues: Partial<IModule> = {
|
const defaultValues: Partial<IModule> = {
|
||||||
lead: "",
|
lead: "",
|
||||||
@ -66,7 +67,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureModuleEvent, captureEvent } = useEventTracker();
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -77,7 +78,19 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const submitChanges = (data: Partial<IModule>) => {
|
const submitChanges = (data: Partial<IModule>) => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data);
|
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data)
|
||||||
|
.then((res) => {
|
||||||
|
captureModuleEvent({
|
||||||
|
eventName: MODULE_UPDATED,
|
||||||
|
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((_) => {
|
||||||
|
captureModuleEvent({
|
||||||
|
eventName: MODULE_UPDATED,
|
||||||
|
payload: { ...data, state: "FAILED" },
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateLink = async (formData: ModuleLink) => {
|
const handleCreateLink = async (formData: ModuleLink) => {
|
||||||
@ -87,6 +100,10 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload)
|
createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
captureEvent(MODULE_LINK_CREATED, {
|
||||||
|
module_id: moduleId,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Module link created",
|
title: "Module link created",
|
||||||
@ -109,6 +126,10 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload)
|
updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
captureEvent(MODULE_LINK_UPDATED, {
|
||||||
|
module_id: moduleId,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Module link updated",
|
title: "Module link updated",
|
||||||
@ -129,6 +150,10 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
deleteModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId)
|
deleteModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
captureEvent(MODULE_LINK_DELETED, {
|
||||||
|
module_id: moduleId,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Module link deleted",
|
title: "Module link deleted",
|
||||||
@ -187,8 +212,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
||||||
submitChanges({
|
submitChanges({
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
|
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
|
||||||
|
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -294,7 +319,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="status"
|
name="status"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
customButton={
|
customButton={
|
||||||
<span
|
<span
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ArchiveRestore, Clock, MessageSquare, MoreVertical, User2 } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Menu } from "@headlessui/react";
|
||||||
|
import { ArchiveRestore, Clock, MessageSquare, MoreVertical, User2 } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// icons
|
// icons
|
||||||
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
@ -13,10 +15,12 @@ import { snoozeOptions } from "constants/notification";
|
|||||||
import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper";
|
import { replaceUnderscoreIfSnakeCase, truncateText, stripAndTruncateHTML } from "helpers/string.helper";
|
||||||
import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
import { calculateTimeAgo, renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
||||||
// type
|
// type
|
||||||
import type { IUserNotification } from "@plane/types";
|
import type { IUserNotification, NotificationType } from "@plane/types";
|
||||||
import { Menu } from "@headlessui/react";
|
// constants
|
||||||
|
import { ISSUE_OPENED, NOTIFICATIONS_READ, NOTIFICATION_ARCHIVED, NOTIFICATION_SNOOZED } from "constants/event-tracker";
|
||||||
|
|
||||||
type NotificationCardProps = {
|
type NotificationCardProps = {
|
||||||
|
selectedTab: NotificationType;
|
||||||
notification: IUserNotification;
|
notification: IUserNotification;
|
||||||
isSnoozedTabOpen: boolean;
|
isSnoozedTabOpen: boolean;
|
||||||
closePopover: () => void;
|
closePopover: () => void;
|
||||||
@ -29,6 +33,7 @@ type NotificationCardProps = {
|
|||||||
|
|
||||||
export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
selectedTab,
|
||||||
notification,
|
notification,
|
||||||
isSnoozedTabOpen,
|
isSnoozedTabOpen,
|
||||||
closePopover,
|
closePopover,
|
||||||
@ -38,6 +43,8 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
setSelectedNotificationForSnooze,
|
setSelectedNotificationForSnooze,
|
||||||
markSnoozeNotification,
|
markSnoozeNotification,
|
||||||
} = props;
|
} = props;
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -115,6 +122,10 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
markNotificationReadStatus(notification.id);
|
markNotificationReadStatus(notification.id);
|
||||||
|
captureEvent(ISSUE_OPENED, {
|
||||||
|
issue_id: notification.data.issue.id,
|
||||||
|
element: "notification",
|
||||||
|
});
|
||||||
closePopover();
|
closePopover();
|
||||||
}}
|
}}
|
||||||
href={`/${workspaceSlug}/projects/${notification.project}/${
|
href={`/${workspaceSlug}/projects/${notification.project}/${
|
||||||
@ -301,15 +312,55 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-3 top-3 hidden gap-x-3 py-1 md:group-hover:flex">
|
<div className="absolute right-3 top-3 hidden gap-x-3 py-1 group-hover:flex">
|
||||||
{moreOptions.map((item) => (
|
{[
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: notification.read_at ? "Mark as unread" : "Mark as read",
|
||||||
|
icon: <MessageSquare className="h-3.5 w-3.5 text-custom-text-300" />,
|
||||||
|
onClick: () => {
|
||||||
|
markNotificationReadStatusToggle(notification.id).then(() => {
|
||||||
|
captureEvent(NOTIFICATIONS_READ, {
|
||||||
|
issue_id: notification.data.issue.id,
|
||||||
|
tab: selectedTab,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.read_at ? "Notification marked as read" : "Notification marked as unread",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: notification.archived_at ? "Unarchive" : "Archive",
|
||||||
|
icon: notification.archived_at ? (
|
||||||
|
<ArchiveRestore className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
) : (
|
||||||
|
<ArchiveIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
markNotificationArchivedStatus(notification.id).then(() => {
|
||||||
|
captureEvent(NOTIFICATION_ARCHIVED, {
|
||||||
|
issue_id: notification.data.issue.id,
|
||||||
|
tab: selectedTab,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
setToastAlert({
|
||||||
|
title: notification.archived_at ? "Notification un-archived" : "Notification archived",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
].map((item) => (
|
||||||
<Tooltip tooltipContent={item.name}>
|
<Tooltip tooltipContent={item.name}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
item.onClick();
|
item.onClick();
|
||||||
}}
|
}}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@ -335,7 +386,23 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
snoozeOptionOnClick(item.value);
|
|
||||||
|
if (!item.value) {
|
||||||
|
setSelectedNotificationForSnooze(notification.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
markSnoozeNotification(notification.id, item.value).then(() => {
|
||||||
|
captureEvent(NOTIFICATION_SNOOZED, {
|
||||||
|
issue_id: notification.data.issue.id,
|
||||||
|
tab: selectedTab,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
setToastAlert({
|
||||||
|
title: `Notification snoozed till ${renderFormattedDate(item.value)}`,
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
@ -4,10 +4,19 @@ import { ArrowLeft, CheckCheck, Clock, ListFilter, MoreVertical, RefreshCw, X }
|
|||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
// ui
|
// ui
|
||||||
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
import { ArchiveIcon, CustomMenu, Tooltip } from "@plane/ui";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// helpers
|
// helpers
|
||||||
import { getNumberCount } from "helpers/string.helper";
|
import { getNumberCount } from "helpers/string.helper";
|
||||||
// type
|
// type
|
||||||
import type { NotificationType, NotificationCount } from "@plane/types";
|
import type { NotificationType, NotificationCount } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import {
|
||||||
|
ARCHIVED_NOTIFICATIONS,
|
||||||
|
NOTIFICATIONS_READ,
|
||||||
|
SNOOZED_NOTIFICATIONS,
|
||||||
|
UNREAD_NOTIFICATIONS,
|
||||||
|
} from "constants/event-tracker";
|
||||||
|
|
||||||
type NotificationHeaderProps = {
|
type NotificationHeaderProps = {
|
||||||
notificationCount?: NotificationCount | null;
|
notificationCount?: NotificationCount | null;
|
||||||
@ -41,6 +50,8 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
setSelectedTab,
|
setSelectedTab,
|
||||||
markAllNotificationsAsRead,
|
markAllNotificationsAsRead,
|
||||||
} = props;
|
} = props;
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
const notificationTabs: Array<{
|
const notificationTabs: Array<{
|
||||||
label: string;
|
label: string;
|
||||||
@ -90,6 +101,7 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
setSnoozed(false);
|
setSnoozed(false);
|
||||||
setArchived(false);
|
setArchived(false);
|
||||||
setReadNotification((prev) => !prev);
|
setReadNotification((prev) => !prev);
|
||||||
|
captureEvent(UNREAD_NOTIFICATIONS);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListFilter className="h-3.5 w-3.5" />
|
<ListFilter className="h-3.5 w-3.5" />
|
||||||
@ -103,7 +115,12 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
}
|
}
|
||||||
closeOnSelect
|
closeOnSelect
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem onClick={markAllNotificationsAsRead}>
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
markAllNotificationsAsRead();
|
||||||
|
captureEvent(NOTIFICATIONS_READ);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCheck className="h-3.5 w-3.5" />
|
<CheckCheck className="h-3.5 w-3.5" />
|
||||||
Mark all as read
|
Mark all as read
|
||||||
@ -114,6 +131,7 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
setArchived(false);
|
setArchived(false);
|
||||||
setReadNotification(false);
|
setReadNotification(false);
|
||||||
setSnoozed((prev) => !prev);
|
setSnoozed((prev) => !prev);
|
||||||
|
captureEvent(SNOOZED_NOTIFICATIONS);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -126,6 +144,7 @@ export const NotificationHeader: React.FC<NotificationHeaderProps> = (props) =>
|
|||||||
setSnoozed(false);
|
setSnoozed(false);
|
||||||
setReadNotification(false);
|
setReadNotification(false);
|
||||||
setArchived((prev) => !prev);
|
setArchived((prev) => !prev);
|
||||||
|
captureEvent(ARCHIVED_NOTIFICATIONS);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -128,6 +128,7 @@ export const NotificationPopover = observer(() => {
|
|||||||
<div className="divide-y divide-custom-border-100">
|
<div className="divide-y divide-custom-border-100">
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<NotificationCard
|
<NotificationCard
|
||||||
|
selectedTab={selectedTab}
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
isSnoozedTabOpen={snoozed}
|
isSnoozedTabOpen={snoozed}
|
||||||
closePopover={() => setIsActive(false)}
|
closePopover={() => setIsActive(false)}
|
||||||
|
@ -11,11 +11,13 @@ import { WorkspaceService } from "services/workspace.service";
|
|||||||
// constants
|
// constants
|
||||||
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
import { MEMBER_ACCEPTED } from "constants/event-tracker";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceMemberInvitation } from "@plane/types";
|
import { IWorkspaceMemberInvitation } from "@plane/types";
|
||||||
// icons
|
// icons
|
||||||
import { CheckCircle2, Search } from "lucide-react";
|
import { CheckCircle2, Search } from "lucide-react";
|
||||||
import {} from "hooks/store/use-event-tracker";
|
import {} from "hooks/store/use-event-tracker";
|
||||||
|
import { getUserRole } from "helpers/user.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleNextStep: () => void;
|
handleNextStep: () => void;
|
||||||
@ -58,11 +60,19 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||||||
if (invitationsRespond.length <= 0) return;
|
if (invitationsRespond.length <= 0) return;
|
||||||
|
|
||||||
setIsJoiningWorkspaces(true);
|
setIsJoiningWorkspaces(true);
|
||||||
|
const invitation = invitations?.find((invitation) => invitation.id === invitationsRespond[0]);
|
||||||
|
|
||||||
await workspaceService
|
await workspaceService
|
||||||
.joinWorkspaces({ invitations: invitationsRespond })
|
.joinWorkspaces({ invitations: invitationsRespond })
|
||||||
.then(async (res) => {
|
.then(async () => {
|
||||||
captureEvent("Member accepted", { ...res, state: "SUCCESS", accepted_from: "App" });
|
captureEvent(MEMBER_ACCEPTED, {
|
||||||
|
member_id: invitation?.id,
|
||||||
|
role: getUserRole(invitation?.role!),
|
||||||
|
project_id: undefined,
|
||||||
|
accepted_from: "App",
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace invitations page",
|
||||||
|
});
|
||||||
await fetchWorkspaces();
|
await fetchWorkspaces();
|
||||||
await mutate(USER_WORKSPACES);
|
await mutate(USER_WORKSPACES);
|
||||||
await updateLastWorkspace();
|
await updateLastWorkspace();
|
||||||
@ -71,7 +81,14 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureEvent("Member accepted", { state: "FAILED", accepted_from: "App" });
|
captureEvent(MEMBER_ACCEPTED, {
|
||||||
|
member_id: invitation?.id,
|
||||||
|
role: getUserRole(invitation?.role!),
|
||||||
|
project_id: undefined,
|
||||||
|
accepted_from: "App",
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Workspace invitations page",
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.finally(() => setIsJoiningWorkspaces(false));
|
.finally(() => setIsJoiningWorkspaces(false));
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,7 @@ import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
|||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -28,6 +29,9 @@ import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
|||||||
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
|
import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
|
||||||
|
import { MEMBER_INVITED } from "constants/event-tracker";
|
||||||
|
// helpers
|
||||||
|
import { getUserRole } from "helpers/user.helper";
|
||||||
// assets
|
// assets
|
||||||
import user1 from "public/users/user-1.png";
|
import user1 from "public/users/user-1.png";
|
||||||
import user2 from "public/users/user-2.png";
|
import user2 from "public/users/user-2.png";
|
||||||
@ -267,6 +271,8 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@ -305,6 +311,17 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
captureEvent(MEMBER_INVITED, {
|
||||||
|
emails: [
|
||||||
|
...payload.emails.map((email) => ({
|
||||||
|
email: email.email,
|
||||||
|
role: getUserRole(email.role),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
project_id: undefined,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Onboarding",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -313,13 +330,18 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
await nextStep();
|
await nextStep();
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(MEMBER_INVITED, {
|
||||||
|
project_id: undefined,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Onboarding",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error,
|
message: err?.error,
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendField = () => {
|
const appendField = () => {
|
||||||
|
@ -15,6 +15,8 @@ import CyclesTour from "public/onboarding/cycles.webp";
|
|||||||
import ModulesTour from "public/onboarding/modules.webp";
|
import ModulesTour from "public/onboarding/modules.webp";
|
||||||
import ViewsTour from "public/onboarding/views.webp";
|
import ViewsTour from "public/onboarding/views.webp";
|
||||||
import PagesTour from "public/onboarding/pages.webp";
|
import PagesTour from "public/onboarding/pages.webp";
|
||||||
|
// constants
|
||||||
|
import { PRODUCT_TOUR_SKIPPED, PRODUCT_TOUR_STARTED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
@ -79,7 +81,7 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||||||
const [step, setStep] = useState<TTourSteps>("welcome");
|
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||||
// store hooks
|
// store hooks
|
||||||
const { commandPalette: commandPaletteStore } = useApplication();
|
const { commandPalette: commandPaletteStore } = useApplication();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement, captureEvent } = useEventTracker();
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
|
|
||||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||||
@ -103,13 +105,22 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex h-full items-end">
|
<div className="flex h-full items-end">
|
||||||
<div className="mt-8 flex items-center gap-6">
|
<div className="mt-8 flex items-center gap-6">
|
||||||
<Button variant="primary" onClick={() => setStep("issues")}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
captureEvent(PRODUCT_TOUR_STARTED);
|
||||||
|
setStep("issues");
|
||||||
|
}}
|
||||||
|
>
|
||||||
Take a Product Tour
|
Take a Product Tour
|
||||||
</Button>
|
</Button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
||||||
onClick={onComplete}
|
onClick={() => {
|
||||||
|
captureEvent(PRODUCT_TOUR_SKIPPED);
|
||||||
|
onComplete();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
No thanks, I will explore it myself
|
No thanks, I will explore it myself
|
||||||
</button>
|
</button>
|
||||||
@ -156,8 +167,8 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setTrackElement("Product tour");
|
||||||
onComplete();
|
onComplete();
|
||||||
setTrackElement("Onboarding tour");
|
|
||||||
commandPaletteStore.toggleCreateProjectModal(true);
|
commandPaletteStore.toggleCreateProjectModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Camera, User2 } from "lucide-react";
|
import { Camera, User2 } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser, useWorkspace } from "hooks/store";
|
import { useEventTracker, useUser, useWorkspace } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
import { OnboardingSidebar, OnboardingStepIndicator } from "components/onboarding";
|
import { OnboardingSidebar, OnboardingStepIndicator } from "components/onboarding";
|
||||||
@ -15,6 +15,7 @@ import { IUser } from "@plane/types";
|
|||||||
import { FileService } from "services/file.service";
|
import { FileService } from "services/file.service";
|
||||||
// assets
|
// assets
|
||||||
import IssuesSvg from "public/onboarding/onboarding-issues.webp";
|
import IssuesSvg from "public/onboarding/onboarding-issues.webp";
|
||||||
|
import { USER_DETAILS } from "constants/event-tracker";
|
||||||
|
|
||||||
const defaultValues: Partial<IUser> = {
|
const defaultValues: Partial<IUser> = {
|
||||||
first_name: "",
|
first_name: "",
|
||||||
@ -48,6 +49,7 @@ export const UserDetails: React.FC<Props> = observer((props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { updateCurrentUser } = useUser();
|
const { updateCurrentUser } = useUser();
|
||||||
const { workspaces } = useWorkspace();
|
const { workspaces } = useWorkspace();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// derived values
|
// derived values
|
||||||
const workspaceName = workspaces ? Object.values(workspaces)?.[0]?.name : "New Workspace";
|
const workspaceName = workspaces ? Object.values(workspaces)?.[0]?.name : "New Workspace";
|
||||||
// form info
|
// form info
|
||||||
@ -76,7 +78,21 @@ export const UserDetails: React.FC<Props> = observer((props) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateCurrentUser(payload);
|
await updateCurrentUser(payload)
|
||||||
|
.then(() => {
|
||||||
|
captureEvent(USER_DETAILS, {
|
||||||
|
use_case: formData.use_case,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Onboarding",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
captureEvent(USER_DETAILS, {
|
||||||
|
use_case: formData.use_case,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Onboarding",
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const handleDelete = (url: string | null | undefined) => {
|
const handleDelete = (url: string | null | undefined) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
@ -5,12 +5,13 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// types
|
// types
|
||||||
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser, useWorkspace } from "hooks/store";
|
import { useEventTracker, useUser, useWorkspace } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
// constants
|
// constants
|
||||||
import { RESTRICTED_URLS } from "constants/workspace";
|
import { RESTRICTED_URLS } from "constants/workspace";
|
||||||
|
import { WORKSPACE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
||||||
@ -33,6 +34,7 @@ export const Workspace: React.FC<Props> = (props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { updateCurrentUser } = useUser();
|
const { updateCurrentUser } = useUser();
|
||||||
const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace();
|
const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace();
|
||||||
|
const { captureWorkspaceEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -46,31 +48,48 @@ export const Workspace: React.FC<Props> = (props) => {
|
|||||||
setSlugError(false);
|
setSlugError(false);
|
||||||
|
|
||||||
await createWorkspace(formData)
|
await createWorkspace(formData)
|
||||||
.then(async () => {
|
.then(async (res) => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Workspace created successfully.",
|
message: "Workspace created successfully.",
|
||||||
});
|
});
|
||||||
|
captureWorkspaceEvent({
|
||||||
|
eventName: WORKSPACE_CREATED,
|
||||||
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
first_time: true,
|
||||||
|
element: "Onboarding",
|
||||||
|
},
|
||||||
|
});
|
||||||
await fetchWorkspaces();
|
await fetchWorkspaces();
|
||||||
await completeStep();
|
await completeStep();
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
|
captureWorkspaceEvent({
|
||||||
|
eventName: WORKSPACE_CREATED,
|
||||||
|
payload: {
|
||||||
|
state: "FAILED",
|
||||||
|
first_time: true,
|
||||||
|
element: "Onboarding",
|
||||||
|
},
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Workspace could not be created. Please try again.",
|
message: "Workspace could not be created. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
} else setSlugError(true);
|
} else setSlugError(true);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Some error occurred while creating workspace. Please try again.",
|
message: "Some error occurred while creating workspace. Please try again.",
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeStep = async () => {
|
const completeStep = async () => {
|
||||||
|
@ -13,6 +13,7 @@ import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
|||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { PRODUCT_TOUR_COMPLETED } from "constants/event-tracker";
|
||||||
|
|
||||||
export const WorkspaceDashboardView = observer(() => {
|
export const WorkspaceDashboardView = observer(() => {
|
||||||
// theme
|
// theme
|
||||||
@ -37,9 +38,8 @@ export const WorkspaceDashboardView = observer(() => {
|
|||||||
const handleTourCompleted = () => {
|
const handleTourCompleted = () => {
|
||||||
updateTourCompleted()
|
updateTourCompleted()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent("User tour complete", {
|
captureEvent(PRODUCT_TOUR_COMPLETED, {
|
||||||
user_id: currentUser?.id,
|
user_id: currentUser?.id,
|
||||||
email: currentUser?.email,
|
|
||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -84,7 +84,7 @@ export const WorkspaceDashboardView = observer(() => {
|
|||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: "Build your first project",
|
text: "Build your first project",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("Dashboard");
|
setTrackElement("Dashboard empty state");
|
||||||
toggleCreateProjectModal(true);
|
toggleCreateProjectModal(true);
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -3,10 +3,14 @@ import { useRouter } from "next/router";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// components
|
// components
|
||||||
import { PageForm } from "./page-form";
|
import { PageForm } from "./page-form";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// types
|
// types
|
||||||
import { IPage } from "@plane/types";
|
import { IPage } from "@plane/types";
|
||||||
import { useProjectPages } from "hooks/store/use-project-page";
|
import { useProjectPages } from "hooks/store/use-project-page";
|
||||||
import { IPageStore } from "store/page.store";
|
import { IPageStore } from "store/page.store";
|
||||||
|
// constants
|
||||||
|
import { PAGE_CREATED, PAGE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
// data?: IPage | null;
|
// data?: IPage | null;
|
||||||
@ -21,12 +25,30 @@ export const CreateUpdatePageModal: FC<Props> = (props) => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
// store hooks
|
||||||
const { createPage } = useProjectPages();
|
const { createPage } = useProjectPages();
|
||||||
|
const { capturePageEvent } = useEventTracker();
|
||||||
|
|
||||||
const createProjectPage = async (payload: IPage) => {
|
const createProjectPage = async (payload: IPage) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
await createPage(workspaceSlug.toString(), projectId, payload);
|
await createPage(workspaceSlug.toString(), projectId, payload)
|
||||||
|
.then((res) => {
|
||||||
|
capturePageEvent({
|
||||||
|
eventName: PAGE_CREATED,
|
||||||
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
capturePageEvent({
|
||||||
|
eventName: PAGE_CREATED,
|
||||||
|
payload: {
|
||||||
|
state: "FAILED",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (formData: IPage) => {
|
const handleFormSubmit = async (formData: IPage) => {
|
||||||
@ -39,6 +61,14 @@ export const CreateUpdatePageModal: FC<Props> = (props) => {
|
|||||||
if (pageStore.access !== formData.access) {
|
if (pageStore.access !== formData.access) {
|
||||||
formData.access === 1 ? await pageStore.makePrivate() : await pageStore.makePublic();
|
formData.access === 1 ? await pageStore.makePrivate() : await pageStore.makePublic();
|
||||||
}
|
}
|
||||||
|
capturePageEvent({
|
||||||
|
eventName: PAGE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...pageStore,
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log("Page updated successfully", pageStore);
|
||||||
} else {
|
} else {
|
||||||
await createProjectPage(formData);
|
await createProjectPage(formData);
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,14 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePage } from "hooks/store";
|
import { useEventTracker, usePage } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { useProjectPages } from "hooks/store/use-project-page";
|
import { useProjectPages } from "hooks/store/use-project-page";
|
||||||
|
// constants
|
||||||
|
import { PAGE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type TConfirmPageDeletionProps = {
|
type TConfirmPageDeletionProps = {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@ -27,6 +29,7 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { deletePage } = useProjectPages();
|
const { deletePage } = useProjectPages();
|
||||||
|
const { capturePageEvent } = useEventTracker();
|
||||||
const pageStore = usePage(pageId);
|
const pageStore = usePage(pageId);
|
||||||
|
|
||||||
// toast alert
|
// toast alert
|
||||||
@ -49,6 +52,13 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||||||
// Delete Page will only delete the page from the archive page map, at this point only archived pages can be deleted
|
// Delete Page will only delete the page from the archive page map, at this point only archived pages can be deleted
|
||||||
await deletePage(workspaceSlug.toString(), projectId as string, pageId)
|
await deletePage(workspaceSlug.toString(), projectId as string, pageId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
capturePageEvent({
|
||||||
|
eventName: PAGE_DELETED,
|
||||||
|
payload: {
|
||||||
|
...pageStore,
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -57,6 +67,13 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
capturePageEvent({
|
||||||
|
eventName: PAGE_DELETED,
|
||||||
|
payload: {
|
||||||
|
...pageStore,
|
||||||
|
state: "FAILED",
|
||||||
|
},
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -18,6 +18,7 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
|
|||||||
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { PROJECT_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -134,13 +135,8 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
};
|
};
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project created",
|
eventName: PROJECT_CREATED,
|
||||||
payload: newPayload,
|
payload: newPayload,
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: res.workspace,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -160,16 +156,11 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
message: err.data[key],
|
message: err.data[key],
|
||||||
});
|
});
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project created",
|
eventName: PROJECT_CREATED,
|
||||||
payload: {
|
payload: {
|
||||||
...payload,
|
...payload,
|
||||||
state: "FAILED",
|
state: "FAILED",
|
||||||
},
|
}
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import type { IProject } from "@plane/types";
|
import type { IProject } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { PROJECT_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type DeleteProjectModal = {
|
type DeleteProjectModal = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -62,13 +64,8 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
|||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project deleted",
|
eventName: PROJECT_DELETED,
|
||||||
payload: { ...project, state: "SUCCESS", element: "Project general settings" },
|
payload: { ...project, state: "SUCCESS", element: "Project general settings" },
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -78,13 +75,8 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project deleted",
|
eventName: PROJECT_DELETED,
|
||||||
payload: { ...project, state: "FAILED", element: "Project general settings" },
|
payload: { ...project, state: "FAILED", element: "Project general settings" },
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -18,6 +18,7 @@ import { renderFormattedDate } from "helpers/date-time.helper";
|
|||||||
import { NETWORK_CHOICES } from "constants/project";
|
import { NETWORK_CHOICES } from "constants/project";
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
|
import { PROJECT_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IProjectDetailsForm {
|
export interface IProjectDetailsForm {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
@ -45,7 +46,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
setValue,
|
setValue,
|
||||||
setError,
|
setError,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors },
|
formState: { errors, dirtyFields },
|
||||||
} = useForm<IProject>({
|
} = useForm<IProject>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...project,
|
...project,
|
||||||
@ -77,13 +78,15 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
|
|
||||||
return updateProject(workspaceSlug.toString(), project.id, payload)
|
return updateProject(workspaceSlug.toString(), project.id, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
const changed_properties = Object.keys(dirtyFields);
|
||||||
|
console.log(dirtyFields);
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project updated",
|
eventName: PROJECT_UPDATED,
|
||||||
payload: { ...res, state: "SUCCESS", element: "Project general settings" },
|
payload: {
|
||||||
group: {
|
...res,
|
||||||
isGrouping: true,
|
changed_properties: changed_properties,
|
||||||
groupType: "Workspace_metrics",
|
state: "SUCCESS",
|
||||||
groupId: res.workspace,
|
element: "Project general settings",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -94,13 +97,8 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: "Project updated",
|
eventName: PROJECT_UPDATED,
|
||||||
payload: { ...payload, state: "FAILED", element: "Project general settings" },
|
payload: { ...payload, state: "FAILED", element: "Project general settings" },
|
||||||
group: {
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -153,7 +151,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
|
||||||
|
|
||||||
<img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" />
|
<img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" />
|
||||||
<div className="absolute bottom-4 z-5 flex w-full items-end justify-between gap-3 px-4">
|
<div className="z-5 absolute bottom-4 flex w-full items-end justify-between gap-3 px-4">
|
||||||
<div className="flex flex-grow gap-3 truncate">
|
<div className="flex flex-grow gap-3 truncate">
|
||||||
<div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90">
|
<div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90">
|
||||||
<div className="grid h-7 w-7 place-items-center">
|
<div className="grid h-7 w-7 place-items-center">
|
||||||
|
@ -11,6 +11,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IProject } from "@plane/types";
|
import { IProject } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { PROJECT_MEMBER_LEAVE } from "constants/event-tracker";
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@ -63,8 +65,9 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
handleClose();
|
handleClose();
|
||||||
router.push(`/${workspaceSlug}/projects`);
|
router.push(`/${workspaceSlug}/projects`);
|
||||||
captureEvent("Project member leave", {
|
captureEvent(PROJECT_MEMBER_LEAVE, {
|
||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
|
element: "Project settings members page",
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -73,8 +76,9 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Something went wrong please try again later.",
|
message: "Something went wrong please try again later.",
|
||||||
});
|
});
|
||||||
captureEvent("Project member leave", {
|
captureEvent(PROJECT_MEMBER_LEAVE, {
|
||||||
state: "FAILED",
|
state: "FAILED",
|
||||||
|
element: "Project settings members page",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember, useProject, useUser } from "hooks/store";
|
import { useEventTracker, useMember, useProject, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { ConfirmProjectMemberRemove } from "components/project";
|
import { ConfirmProjectMemberRemove } from "components/project";
|
||||||
@ -14,6 +14,7 @@ import { ChevronDown, Dot, XCircle } from "lucide-react";
|
|||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { PROJECT_MEMBER_LEAVE } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -35,6 +36,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
project: { removeMemberFromProject, getProjectMemberDetails, updateMember },
|
project: { removeMemberFromProject, getProjectMemberDetails, updateMember },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -48,8 +50,11 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||||||
if (userDetails.member.id === currentUser?.id) {
|
if (userDetails.member.id === currentUser?.id) {
|
||||||
await leaveProject(workspaceSlug.toString(), projectId.toString())
|
await leaveProject(workspaceSlug.toString(), projectId.toString())
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
captureEvent(PROJECT_MEMBER_LEAVE, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Project settings members page",
|
||||||
|
});
|
||||||
await fetchProjects(workspaceSlug.toString());
|
await fetchProjects(workspaceSlug.toString());
|
||||||
|
|
||||||
router.push(`/${workspaceSlug}/projects`);
|
router.push(`/${workspaceSlug}/projects`);
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) =>
|
||||||
|
@ -9,9 +9,12 @@ import { useEventTracker, useMember, useUser, useWorkspace } from "hooks/store";
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
|
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { getUserRole } from "helpers/user.helper";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import { PROJECT_MEMBER_ADDED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -49,7 +52,6 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
const {
|
const {
|
||||||
project: { projectMemberIds, bulkAddMembersToProject },
|
project: { projectMemberIds, bulkAddMembersToProject },
|
||||||
workspace: { workspaceMemberIds, getWorkspaceMemberDetails },
|
workspace: { workspaceMemberIds, getWorkspaceMemberDetails },
|
||||||
@ -79,7 +81,7 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||||||
const payload = { ...formData };
|
const payload = { ...formData };
|
||||||
|
|
||||||
await bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload)
|
await bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
if (onSuccess) onSuccess();
|
if (onSuccess) onSuccess();
|
||||||
onClose();
|
onClose();
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -87,32 +89,23 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||||||
type: "success",
|
type: "success",
|
||||||
message: "Members added successfully.",
|
message: "Members added successfully.",
|
||||||
});
|
});
|
||||||
captureEvent(
|
captureEvent(PROJECT_MEMBER_ADDED, {
|
||||||
"Member added",
|
members: [
|
||||||
{
|
...payload.members.map((member) => ({
|
||||||
...res,
|
member_id: member.member_id,
|
||||||
state: "SUCCESS",
|
role: ROLE[member.role],
|
||||||
},
|
})),
|
||||||
{
|
],
|
||||||
isGrouping: true,
|
state: "SUCCESS",
|
||||||
groupType: "Workspace_metrics",
|
element: "Project settings members page",
|
||||||
groupId: currentWorkspace?.id!,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
captureEvent(
|
captureEvent(PROJECT_MEMBER_ADDED, {
|
||||||
"Member added",
|
state: "FAILED",
|
||||||
{
|
element: "Project settings members page",
|
||||||
state: "FAILED",
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
isGrouping: true,
|
|
||||||
groupType: "Workspace_metrics",
|
|
||||||
groupId: currentWorkspace?.id!,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
|
@ -51,12 +51,11 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { setTrackElement, captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
currentUser,
|
currentUser,
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
const { currentProjectDetails, updateProject } = useProject();
|
const { currentProjectDetails, updateProject } = useProject();
|
||||||
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
|
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
|
||||||
// toast alert
|
// toast alert
|
||||||
@ -91,14 +90,9 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
|
|||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
value={Boolean(currentProjectDetails?.[feature.property as keyof IProject])}
|
value={Boolean(currentProjectDetails?.[feature.property as keyof IProject])}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setTrackElement("PROJECT_SETTINGS_FEATURES_PAGE");
|
|
||||||
captureEvent(`Toggle ${feature.title.toLowerCase()}`, {
|
captureEvent(`Toggle ${feature.title.toLowerCase()}`, {
|
||||||
workspace_id: currentWorkspace?.id,
|
|
||||||
workspace_slug: currentWorkspace?.slug,
|
|
||||||
project_id: currentProjectDetails?.id,
|
|
||||||
project_name: currentProjectDetails?.name,
|
|
||||||
project_identifier: currentProjectDetails?.identifier,
|
|
||||||
enabled: !currentProjectDetails?.[feature.property as keyof IProject],
|
enabled: !currentProjectDetails?.[feature.property as keyof IProject],
|
||||||
|
element: "Project settings feature page",
|
||||||
});
|
});
|
||||||
handleSubmit({
|
handleSubmit({
|
||||||
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],
|
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],
|
||||||
|
@ -13,6 +13,7 @@ import { Button, CustomSelect, Input, Tooltip } from "@plane/ui";
|
|||||||
import type { IState } from "@plane/types";
|
import type { IState } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { GROUP_CHOICES } from "constants/project";
|
import { GROUP_CHOICES } from "constants/project";
|
||||||
|
import { STATE_CREATED, STATE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: IState | null;
|
data: IState | null;
|
||||||
@ -36,7 +37,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent, setTrackElement } = useEventTracker();
|
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||||
const { createState, updateState } = useProjectState();
|
const { createState, updateState } = useProjectState();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -86,9 +87,13 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "State created successfully.",
|
message: "State created successfully.",
|
||||||
});
|
});
|
||||||
captureEvent("State created", {
|
captureProjectStateEvent({
|
||||||
...res,
|
eventName: STATE_CREATED,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Project settings states page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -104,8 +109,14 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "State could not be created. Please try again.",
|
message: "State could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureEvent("State created", {
|
|
||||||
state: "FAILED",
|
captureProjectStateEvent({
|
||||||
|
eventName: STATE_CREATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Project settings states page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -116,9 +127,13 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||||||
await updateState(workspaceSlug.toString(), projectId.toString(), data.id, formData)
|
await updateState(workspaceSlug.toString(), projectId.toString(), data.id, formData)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
handleClose();
|
handleClose();
|
||||||
captureEvent("State updated", {
|
captureProjectStateEvent({
|
||||||
...res,
|
eventName: STATE_UPDATED,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Project settings states page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -139,8 +154,13 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "State could not be updated. Please try again.",
|
message: "State could not be updated. Please try again.",
|
||||||
});
|
});
|
||||||
captureEvent("State updated", {
|
captureProjectStateEvent({
|
||||||
state: "FAILED",
|
eventName: STATE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
...formData,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Project settings states page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import type { IState } from "@plane/types";
|
import type { IState } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { STATE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -25,7 +27,7 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureProjectStateEvent } = useEventTracker();
|
||||||
const { deleteState } = useProjectState();
|
const { deleteState } = useProjectState();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -42,8 +44,12 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent("State deleted", {
|
captureProjectStateEvent({
|
||||||
state: "SUCCESS",
|
eventName: STATE_DELETED,
|
||||||
|
payload: {
|
||||||
|
...data,
|
||||||
|
state: "SUCCESS",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
@ -61,8 +67,12 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "State could not be deleted. Please try again.",
|
message: "State could not be deleted. Please try again.",
|
||||||
});
|
});
|
||||||
captureEvent("State deleted", {
|
captureProjectStateEvent({
|
||||||
state: "FAILED",
|
eventName: STATE_DELETED,
|
||||||
|
payload: {
|
||||||
|
...data,
|
||||||
|
state: "FAILED",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
@ -13,6 +13,7 @@ import { Button, CustomSelect, Input } from "@plane/ui";
|
|||||||
import { IWorkspace } from "@plane/types";
|
import { IWorkspace } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "constants/workspace";
|
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "constants/workspace";
|
||||||
|
import { WORKSPACE_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSubmit?: (res: IWorkspace) => Promise<void>;
|
onSubmit?: (res: IWorkspace) => Promise<void>;
|
||||||
@ -48,7 +49,7 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureWorkspaceEvent } = useEventTracker();
|
||||||
const { createWorkspace } = useWorkspace();
|
const { createWorkspace } = useWorkspace();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -70,9 +71,13 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
await createWorkspace(formData)
|
await createWorkspace(formData)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
captureEvent("Workspace created", {
|
captureWorkspaceEvent({
|
||||||
...res,
|
eventName: WORKSPACE_CREATED,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Create workspace page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -83,14 +88,18 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
if (onSubmit) await onSubmit(res);
|
if (onSubmit) await onSubmit(res);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
captureWorkspaceEvent({
|
||||||
|
eventName: WORKSPACE_CREATED,
|
||||||
|
payload: {
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Create workspace page",
|
||||||
|
},
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Workspace could not be created. Please try again.",
|
message: "Workspace could not be created. Please try again.",
|
||||||
});
|
});
|
||||||
captureEvent("Workspace created", {
|
|
||||||
state: "FAILED",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else setSlugError(true);
|
} else setSlugError(true);
|
||||||
})
|
})
|
||||||
@ -100,9 +109,6 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Some error occurred while creating workspace. Please try again.",
|
message: "Some error occurred while creating workspace. Please try again.",
|
||||||
});
|
});
|
||||||
captureEvent("Workspace created", {
|
|
||||||
state: "FAILED",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { Button, Input } from "@plane/ui";
|
import { Button, Input } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspace } from "@plane/types";
|
import type { IWorkspace } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { WORKSPACE_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -28,7 +30,7 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureWorkspaceEvent } = useEventTracker();
|
||||||
const { deleteWorkspace } = useWorkspace();
|
const { deleteWorkspace } = useWorkspace();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -59,9 +61,13 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
handleClose();
|
handleClose();
|
||||||
router.push("/");
|
router.push("/");
|
||||||
captureEvent("Workspace deleted", {
|
captureWorkspaceEvent({
|
||||||
res,
|
eventName: WORKSPACE_DELETED,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
...data,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace general settings page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -75,8 +81,13 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
|||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Something went wrong. Please try again later.",
|
message: "Something went wrong. Please try again later.",
|
||||||
});
|
});
|
||||||
captureEvent("Workspace deleted", {
|
captureWorkspaceEvent({
|
||||||
state: "FAILED",
|
eventName: WORKSPACE_DELETED,
|
||||||
|
payload: {
|
||||||
|
...data,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Workspace general settings page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ChevronDown, Dot, XCircle } from "lucide-react";
|
import { ChevronDown, Dot, XCircle } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember, useUser } from "hooks/store";
|
import { useEventTracker, useMember, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { ConfirmWorkspaceMemberRemove } from "components/workspace";
|
import { ConfirmWorkspaceMemberRemove } from "components/workspace";
|
||||||
@ -12,6 +12,7 @@ import { ConfirmWorkspaceMemberRemove } from "components/workspace";
|
|||||||
import { CustomSelect, Tooltip } from "@plane/ui";
|
import { CustomSelect, Tooltip } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
|
import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
|
||||||
|
import { WORKSPACE_MEMBER_lEAVE } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
memberId: string;
|
memberId: string;
|
||||||
@ -33,6 +34,7 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
workspace: { updateMember, removeMemberFromWorkspace, getWorkspaceMemberDetails },
|
workspace: { updateMember, removeMemberFromWorkspace, getWorkspaceMemberDetails },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// derived values
|
// derived values
|
||||||
@ -42,7 +44,13 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||||||
if (!workspaceSlug || !currentUserSettings) return;
|
if (!workspaceSlug || !currentUserSettings) return;
|
||||||
|
|
||||||
await leaveWorkspace(workspaceSlug.toString())
|
await leaveWorkspace(workspaceSlug.toString())
|
||||||
.then(() => router.push("/profile"))
|
.then(() => {
|
||||||
|
captureEvent(WORKSPACE_MEMBER_lEAVE, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace settings members page",
|
||||||
|
});
|
||||||
|
router.push("/profile");
|
||||||
|
})
|
||||||
.catch((err) =>
|
.catch((err) =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -19,6 +19,7 @@ import { copyUrlToClipboard } from "helpers/string.helper";
|
|||||||
import { IWorkspace } from "@plane/types";
|
import { IWorkspace } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles, ORGANIZATION_SIZE } from "constants/workspace";
|
import { EUserWorkspaceRoles, ORGANIZATION_SIZE } from "constants/workspace";
|
||||||
|
import { WORKSPACE_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
const defaultValues: Partial<IWorkspace> = {
|
const defaultValues: Partial<IWorkspace> = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -37,7 +38,7 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
const [isImageRemoving, setIsImageRemoving] = useState(false);
|
const [isImageRemoving, setIsImageRemoving] = useState(false);
|
||||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureWorkspaceEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceRole },
|
membership: { currentWorkspaceRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -68,9 +69,13 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
|
|
||||||
await updateWorkspace(currentWorkspace.slug, payload)
|
await updateWorkspace(currentWorkspace.slug, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
captureEvent("Workspace updated", {
|
captureWorkspaceEvent({
|
||||||
...res,
|
eventName: WORKSPACE_UPDATED,
|
||||||
state: "SUCCESS",
|
payload: {
|
||||||
|
...res,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace general settings page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
@ -79,8 +84,12 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureEvent("Workspace updated", {
|
captureWorkspaceEvent({
|
||||||
state: "FAILED",
|
eventName: WORKSPACE_UPDATED,
|
||||||
|
payload: {
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Workspace general settings page",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
@ -222,7 +222,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
<div className="flex w-full flex-col items-start justify-start gap-2 px-4 py-2 text-sm">
|
<div className="flex w-full flex-col items-start justify-start gap-2 px-4 py-2 text-sm">
|
||||||
<Link
|
<Link
|
||||||
href="/create-workspace"
|
href="/create-workspace"
|
||||||
onClick={() => setTrackElement("APP_SIDEBAR_WORKSPACE_DROPDOWN")}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
@ -3,7 +3,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { NotificationPopover } from "components/notifications";
|
import { NotificationPopover } from "components/notifications";
|
||||||
// ui
|
// ui
|
||||||
@ -12,12 +12,14 @@ import { Crown } from "lucide-react";
|
|||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
import { SIDEBAR_MENU_ITEMS } from "constants/dashboard";
|
import { SIDEBAR_MENU_ITEMS } from "constants/dashboard";
|
||||||
|
import { SIDEBAR_CLICKED } from "constants/event-tracker";
|
||||||
// helper
|
// helper
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
|
|
||||||
export const WorkspaceSidebarMenu = observer(() => {
|
export const WorkspaceSidebarMenu = observer(() => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { theme: themeStore } = useApplication();
|
const { theme: themeStore } = useApplication();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceRole },
|
membership: { currentWorkspaceRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -27,10 +29,13 @@ export const WorkspaceSidebarMenu = observer(() => {
|
|||||||
// computed
|
// computed
|
||||||
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
||||||
|
|
||||||
const handleLinkClick = () => {
|
const handleLinkClick = (itemKey: string) => {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
themeStore.toggleSidebar();
|
themeStore.toggleSidebar();
|
||||||
}
|
}
|
||||||
|
captureEvent(SIDEBAR_CLICKED, {
|
||||||
|
destination: itemKey,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -38,11 +43,8 @@ export const WorkspaceSidebarMenu = observer(() => {
|
|||||||
{SIDEBAR_MENU_ITEMS.map(
|
{SIDEBAR_MENU_ITEMS.map(
|
||||||
(link) =>
|
(link) =>
|
||||||
workspaceMemberInfo >= link.access && (
|
workspaceMemberInfo >= link.access && (
|
||||||
<Link key={link.key}
|
<Link key={link.key} href={`/${workspaceSlug}${link.href}`} onClick={() => handleLinkClick(link.key)}>
|
||||||
href={`/${workspaceSlug}${link.href}`}
|
<span className="my-1 block w-full">
|
||||||
onClick={handleLinkClick}
|
|
||||||
>
|
|
||||||
<span className="block w-full my-1">
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent={link.label}
|
tooltipContent={link.label}
|
||||||
position="right"
|
position="right"
|
||||||
@ -50,10 +52,11 @@ export const WorkspaceSidebarMenu = observer(() => {
|
|||||||
disabled={!themeStore?.sidebarCollapsed}
|
disabled={!themeStore?.sidebarCollapsed}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${link.highlight(router.asPath, `/${workspaceSlug}`)
|
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
link.highlight(router.asPath, `/${workspaceSlug}`)
|
||||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||||
|
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
<link.Icon
|
<link.Icon
|
||||||
|
@ -4,12 +4,14 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useGlobalView } from "hooks/store";
|
import { useGlobalView, useEventTracker } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceView } from "@plane/types";
|
import { IWorkspaceView } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { GLOBAL_VIEW_DELETED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: IWorkspaceView;
|
data: IWorkspaceView;
|
||||||
@ -26,6 +28,7 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { deleteGlobalView } = useGlobalView();
|
const { deleteGlobalView } = useGlobalView();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -39,13 +42,23 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
|||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
await deleteGlobalView(workspaceSlug.toString(), data.id)
|
await deleteGlobalView(workspaceSlug.toString(), data.id)
|
||||||
.catch(() =>
|
.then(() => {
|
||||||
|
captureEvent(GLOBAL_VIEW_DELETED, {
|
||||||
|
view_id: data.id,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
captureEvent(GLOBAL_VIEW_DELETED, {
|
||||||
|
view_id: data.id,
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Something went wrong while deleting the view. Please try again.",
|
message: "Something went wrong while deleting the view. Please try again.",
|
||||||
})
|
});
|
||||||
)
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
handleClose();
|
handleClose();
|
||||||
|
@ -4,11 +4,12 @@ import Link from "next/link";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useGlobalView, useUser } from "hooks/store";
|
import { useEventTracker, useGlobalView, useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
|
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
|
||||||
// constants
|
// constants
|
||||||
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { GLOBAL_VIEW_OPENED } from "constants/event-tracker";
|
||||||
|
|
||||||
const ViewTab = observer((props: { viewId: string }) => {
|
const ViewTab = observer((props: { viewId: string }) => {
|
||||||
const { viewId } = props;
|
const { viewId } = props;
|
||||||
@ -49,11 +50,19 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceRole },
|
membership: { currentWorkspaceRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
// bring the active view to the centre of the header
|
// bring the active view to the centre of the header
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!globalViewId) return;
|
if (!globalViewId) return;
|
||||||
|
|
||||||
|
captureEvent(GLOBAL_VIEW_OPENED, {
|
||||||
|
view_id: globalViewId,
|
||||||
|
view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||||
|
? "Default"
|
||||||
|
: "Custom",
|
||||||
|
});
|
||||||
|
|
||||||
const activeTabElement = document.querySelector(`#global-view-${globalViewId.toString()}`);
|
const activeTabElement = document.querySelector(`#global-view-${globalViewId.toString()}`);
|
||||||
|
|
||||||
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
|
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
|
||||||
|
@ -3,12 +3,14 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useGlobalView } from "hooks/store";
|
import { useEventTracker, useGlobalView } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { WorkspaceViewForm } from "components/workspace";
|
import { WorkspaceViewForm } from "components/workspace";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceView } from "@plane/types";
|
import { IWorkspaceView } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { GLOBAL_VIEW_CREATED, GLOBAL_VIEW_UPDATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data?: IWorkspaceView;
|
data?: IWorkspaceView;
|
||||||
@ -24,6 +26,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { createGlobalView, updateGlobalView } = useGlobalView();
|
const { createGlobalView, updateGlobalView } = useGlobalView();
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast alert
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -43,6 +46,11 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
|
|
||||||
await createGlobalView(workspaceSlug.toString(), payloadData)
|
await createGlobalView(workspaceSlug.toString(), payloadData)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
captureEvent(GLOBAL_VIEW_CREATED, {
|
||||||
|
view_id: res.id,
|
||||||
|
applied_filters: res.filters,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -52,13 +60,17 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
router.push(`/${workspaceSlug}/workspace-views/${res.id}`);
|
router.push(`/${workspaceSlug}/workspace-views/${res.id}`);
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
|
captureEvent(GLOBAL_VIEW_CREATED, {
|
||||||
|
applied_filters: payload?.filters,
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "View could not be created. Please try again.",
|
message: "View could not be created. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateView = async (payload: Partial<IWorkspaceView>) => {
|
const handleUpdateView = async (payload: Partial<IWorkspaceView>) => {
|
||||||
@ -72,7 +84,12 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
};
|
};
|
||||||
|
|
||||||
await updateGlobalView(workspaceSlug.toString(), data.id, payloadData)
|
await updateGlobalView(workspaceSlug.toString(), data.id, payloadData)
|
||||||
.then(() => {
|
.then((res) => {
|
||||||
|
captureEvent(GLOBAL_VIEW_UPDATED, {
|
||||||
|
view_id: res.id,
|
||||||
|
applied_filters: res.filters,
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -80,13 +97,18 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
});
|
});
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() => {
|
||||||
|
captureEvent(GLOBAL_VIEW_UPDATED, {
|
||||||
|
view_id: data.id,
|
||||||
|
applied_filters: data.filters,
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "View could not be updated. Please try again.",
|
message: "View could not be updated. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (formData: Partial<IWorkspaceView>) => {
|
const handleFormSubmit = async (formData: Partial<IWorkspaceView>) => {
|
||||||
|
@ -4,7 +4,7 @@ import Link from "next/link";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Pencil, Trash2 } from "lucide-react";
|
import { Pencil, Trash2 } from "lucide-react";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useGlobalView } from "hooks/store";
|
import { useEventTracker, useGlobalView } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "components/workspace";
|
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
@ -25,6 +25,7 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getViewDetailsById } = useGlobalView();
|
const { getViewDetailsById } = useGlobalView();
|
||||||
|
const {setTrackElement} = useEventTracker();
|
||||||
// derived data
|
// derived data
|
||||||
const view = getViewDetailsById(viewId);
|
const view = getViewDetailsById(viewId);
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
setTrackElement("List view");
|
||||||
setUpdateViewModal(true);
|
setUpdateViewModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -2,26 +2,31 @@ export type IssueEventProps = {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
payload: any;
|
payload: any;
|
||||||
updates?: any;
|
updates?: any;
|
||||||
group?: EventGroupProps;
|
|
||||||
path?: string;
|
path?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventProps = {
|
export type EventProps = {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
payload: any;
|
payload: any;
|
||||||
group?: EventGroupProps;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EventGroupProps = {
|
export const getWorkspaceEventPayload = (payload: any) => ({
|
||||||
isGrouping?: boolean;
|
workspace_id: payload.id,
|
||||||
groupType?: string;
|
created_at: payload.created_at,
|
||||||
groupId?: string;
|
updated_at: payload.updated_at,
|
||||||
};
|
organization_size: payload.organization_size,
|
||||||
|
first_time: payload.first_time,
|
||||||
|
state: payload.state,
|
||||||
|
element: payload.element,
|
||||||
|
});
|
||||||
|
|
||||||
export const getProjectEventPayload = (payload: any) => ({
|
export const getProjectEventPayload = (payload: any) => ({
|
||||||
workspace_id: payload.workspace_id,
|
workspace_id: payload.workspace_id,
|
||||||
project_id: payload.id,
|
project_id: payload.id,
|
||||||
identifier: payload.identifier,
|
identifier: payload.identifier,
|
||||||
|
project_visibility: payload.network == 2 ? "Public" : "Private",
|
||||||
|
changed_properties: payload.changed_properties,
|
||||||
|
lead_id: payload.project_lead,
|
||||||
created_at: payload.created_at,
|
created_at: payload.created_at,
|
||||||
updated_at: payload.updated_at,
|
updated_at: payload.updated_at,
|
||||||
state: payload.state,
|
state: payload.state,
|
||||||
@ -30,26 +35,43 @@ export const getProjectEventPayload = (payload: any) => ({
|
|||||||
|
|
||||||
export const getCycleEventPayload = (payload: any) => ({
|
export const getCycleEventPayload = (payload: any) => ({
|
||||||
workspace_id: payload.workspace_id,
|
workspace_id: payload.workspace_id,
|
||||||
project_id: payload.id,
|
project_id: payload.project,
|
||||||
cycle_id: payload.id,
|
cycle_id: payload.id,
|
||||||
created_at: payload.created_at,
|
created_at: payload.created_at,
|
||||||
updated_at: payload.updated_at,
|
updated_at: payload.updated_at,
|
||||||
start_date: payload.start_date,
|
start_date: payload.start_date,
|
||||||
target_date: payload.target_date,
|
target_date: payload.target_date,
|
||||||
cycle_status: payload.status,
|
cycle_status: payload.status,
|
||||||
|
changed_properties: payload.changed_properties,
|
||||||
state: payload.state,
|
state: payload.state,
|
||||||
element: payload.element,
|
element: payload.element,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getModuleEventPayload = (payload: any) => ({
|
export const getModuleEventPayload = (payload: any) => ({
|
||||||
workspace_id: payload.workspace_id,
|
workspace_id: payload.workspace_id,
|
||||||
project_id: payload.id,
|
project_id: payload.project,
|
||||||
module_id: payload.id,
|
module_id: payload.id,
|
||||||
created_at: payload.created_at,
|
created_at: payload.created_at,
|
||||||
updated_at: payload.updated_at,
|
updated_at: payload.updated_at,
|
||||||
start_date: payload.start_date,
|
start_date: payload.start_date,
|
||||||
target_date: payload.target_date,
|
target_date: payload.target_date,
|
||||||
module_status: payload.status,
|
module_status: payload.status,
|
||||||
|
lead_id: payload.lead,
|
||||||
|
changed_properties: payload.changed_properties,
|
||||||
|
member_ids: payload.members,
|
||||||
|
state: payload.state,
|
||||||
|
element: payload.element,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getPageEventPayload = (payload: any) => ({
|
||||||
|
workspace_id: payload.workspace_id,
|
||||||
|
project_id: payload.project,
|
||||||
|
created_at: payload.created_at,
|
||||||
|
updated_at: payload.updated_at,
|
||||||
|
access: payload.access === 0 ? "Public" : "Private",
|
||||||
|
is_locked: payload.is_locked,
|
||||||
|
archived_at: payload.archived_at,
|
||||||
|
created_by: payload.created_by,
|
||||||
state: payload.state,
|
state: payload.state,
|
||||||
element: payload.element,
|
element: payload.element,
|
||||||
});
|
});
|
||||||
@ -71,6 +93,7 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
|||||||
sub_issues_count: payload.sub_issues_count,
|
sub_issues_count: payload.sub_issues_count,
|
||||||
parent_id: payload.parent_id,
|
parent_id: payload.parent_id,
|
||||||
project_id: payload.project_id,
|
project_id: payload.project_id,
|
||||||
|
workspace_id: payload.workspace_id,
|
||||||
priority: payload.priority,
|
priority: payload.priority,
|
||||||
state_id: payload.state_id,
|
state_id: payload.state_id,
|
||||||
start_date: payload.start_date,
|
start_date: payload.start_date,
|
||||||
@ -82,7 +105,7 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
|||||||
view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "",
|
view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (eventName === "Issue updated") {
|
if (eventName === ISSUE_UPDATED) {
|
||||||
eventPayload = {
|
eventPayload = {
|
||||||
...eventPayload,
|
...eventPayload,
|
||||||
...updates,
|
...updates,
|
||||||
@ -103,3 +126,99 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
|||||||
}
|
}
|
||||||
return eventPayload;
|
return eventPayload;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getProjectStateEventPayload = (payload: any) => {
|
||||||
|
return {
|
||||||
|
workspace_id: payload.workspace_id,
|
||||||
|
project_id: payload.id,
|
||||||
|
state_id: payload.id,
|
||||||
|
created_at: payload.created_at,
|
||||||
|
updated_at: payload.updated_at,
|
||||||
|
group: payload.group,
|
||||||
|
color: payload.color,
|
||||||
|
default: payload.default,
|
||||||
|
state: payload.state,
|
||||||
|
element: payload.element,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Workspace crud Events
|
||||||
|
export const WORKSPACE_CREATED = "Workspace created";
|
||||||
|
export const WORKSPACE_UPDATED = "Workspace updated";
|
||||||
|
export const WORKSPACE_DELETED = "Workspace deleted";
|
||||||
|
// Project Events
|
||||||
|
export const PROJECT_CREATED = "Project created";
|
||||||
|
export const PROJECT_UPDATED = "Project updated";
|
||||||
|
export const PROJECT_DELETED = "Project deleted";
|
||||||
|
// Cycle Events
|
||||||
|
export const CYCLE_CREATED = "Cycle created";
|
||||||
|
export const CYCLE_UPDATED = "Cycle updated";
|
||||||
|
export const CYCLE_DELETED = "Cycle deleted";
|
||||||
|
export const CYCLE_FAVORITED = "Cycle favorited";
|
||||||
|
export const CYCLE_UNFAVORITED = "Cycle unfavorited";
|
||||||
|
// Module Events
|
||||||
|
export const MODULE_CREATED = "Module created";
|
||||||
|
export const MODULE_UPDATED = "Module updated";
|
||||||
|
export const MODULE_DELETED = "Module deleted";
|
||||||
|
export const MODULE_FAVORITED = "Module favorited";
|
||||||
|
export const MODULE_UNFAVORITED = "Module unfavorited";
|
||||||
|
export const MODULE_LINK_CREATED = "Module link created";
|
||||||
|
export const MODULE_LINK_UPDATED = "Module link updated";
|
||||||
|
export const MODULE_LINK_DELETED = "Module link deleted";
|
||||||
|
// Issue Events
|
||||||
|
export const ISSUE_CREATED = "Issue created";
|
||||||
|
export const ISSUE_UPDATED = "Issue updated";
|
||||||
|
export const ISSUE_DELETED = "Issue deleted";
|
||||||
|
export const ISSUE_OPENED = "Issue opened";
|
||||||
|
// Project State Events
|
||||||
|
export const STATE_CREATED = "State created";
|
||||||
|
export const STATE_UPDATED = "State updated";
|
||||||
|
export const STATE_DELETED = "State deleted";
|
||||||
|
// Project Page Events
|
||||||
|
export const PAGE_CREATED = "Page created";
|
||||||
|
export const PAGE_UPDATED = "Page updated";
|
||||||
|
export const PAGE_DELETED = "Page deleted";
|
||||||
|
// Member Events
|
||||||
|
export const MEMBER_INVITED = "Member invited";
|
||||||
|
export const MEMBER_ACCEPTED = "Member accepted";
|
||||||
|
export const PROJECT_MEMBER_ADDED = "Project member added";
|
||||||
|
export const PROJECT_MEMBER_LEAVE = "Project member leave";
|
||||||
|
export const WORKSPACE_MEMBER_lEAVE = "Workspace member leave";
|
||||||
|
// Sign-in & Sign-up Events
|
||||||
|
export const NAVIGATE_TO_SIGNUP = "Navigate to sign-up page";
|
||||||
|
export const NAVIGATE_TO_SIGNIN = "Navigate to sign-in page";
|
||||||
|
export const CODE_VERIFIED = "Code verified";
|
||||||
|
export const SETUP_PASSWORD = "Password setup";
|
||||||
|
export const PASSWORD_CREATE_SELECTED = "Password created";
|
||||||
|
export const PASSWORD_CREATE_SKIPPED = "Skipped to setup";
|
||||||
|
export const SIGN_IN_WITH_PASSWORD = "Sign in with password";
|
||||||
|
export const FORGOT_PASSWORD = "Forgot password clicked";
|
||||||
|
export const FORGOT_PASS_LINK = "Forgot password link generated";
|
||||||
|
export const NEW_PASS_CREATED = "New password created";
|
||||||
|
// Onboarding Events
|
||||||
|
export const USER_DETAILS = "User details added";
|
||||||
|
export const USER_ONBOARDING_COMPLETED = "User onboarding completed";
|
||||||
|
// Product Tour Events
|
||||||
|
export const PRODUCT_TOUR_STARTED = "Product tour started";
|
||||||
|
export const PRODUCT_TOUR_COMPLETED = "Product tour completed";
|
||||||
|
export const PRODUCT_TOUR_SKIPPED = "Product tour skipped";
|
||||||
|
// Dashboard Events
|
||||||
|
export const CHANGELOG_REDIRECTED = "Changelog redirected";
|
||||||
|
export const GITHUB_REDIRECTED = "Github redirected";
|
||||||
|
// Sidebar Events
|
||||||
|
export const SIDEBAR_CLICKED = "Sidenav clicked";
|
||||||
|
// Global View Events
|
||||||
|
export const GLOBAL_VIEW_CREATED = "Global view created";
|
||||||
|
export const GLOBAL_VIEW_UPDATED = "Global view updated";
|
||||||
|
export const GLOBAL_VIEW_DELETED = "Global view deleted";
|
||||||
|
export const GLOBAL_VIEW_OPENED = "Global view opened";
|
||||||
|
// Notification Events
|
||||||
|
export const NOTIFICATION_ARCHIVED = "Notification archived";
|
||||||
|
export const NOTIFICATION_SNOOZED = "Notification snoozed";
|
||||||
|
export const NOTIFICATION_READ = "Notification marked read";
|
||||||
|
export const UNREAD_NOTIFICATIONS = "Unread notifications viewed";
|
||||||
|
export const NOTIFICATIONS_READ = "All notifications marked read";
|
||||||
|
export const SNOOZED_NOTIFICATIONS= "Snoozed notifications viewed";
|
||||||
|
export const ARCHIVED_NOTIFICATIONS = "Archived notifications viewed";
|
||||||
|
// Groups
|
||||||
|
export const GROUP_WORKSPACE = "Workspace_metrics";
|
||||||
|
@ -5,7 +5,7 @@ import NProgress from "nprogress";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useUser } from "hooks/store";
|
import { useApplication, useUser, useWorkspace } from "hooks/store";
|
||||||
// constants
|
// constants
|
||||||
import { THEMES } from "constants/themes";
|
import { THEMES } from "constants/themes";
|
||||||
// layouts
|
// layouts
|
||||||
@ -37,6 +37,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
|||||||
currentUser,
|
currentUser,
|
||||||
membership: { currentProjectRole, currentWorkspaceRole },
|
membership: { currentProjectRole, currentWorkspaceRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
const {
|
const {
|
||||||
config: { envConfig },
|
config: { envConfig },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
@ -49,6 +50,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
|||||||
<CrispWrapper user={currentUser}>
|
<CrispWrapper user={currentUser}>
|
||||||
<PostHogProvider
|
<PostHogProvider
|
||||||
user={currentUser}
|
user={currentUser}
|
||||||
|
currentWorkspaceId= {currentWorkspace?.id}
|
||||||
workspaceRole={currentWorkspaceRole}
|
workspaceRole={currentWorkspaceRole}
|
||||||
projectRole={currentProjectRole}
|
projectRole={currentProjectRole}
|
||||||
posthogAPIKey={envConfig?.posthog_api_key || null}
|
posthogAPIKey={envConfig?.posthog_api_key || null}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, ReactNode, useEffect } from "react";
|
import { FC, ReactNode, useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
import { PostHogProvider as PHProvider } from "posthog-js/react";
|
import { PostHogProvider as PHProvider } from "posthog-js/react";
|
||||||
@ -6,10 +6,13 @@ import { PostHogProvider as PHProvider } from "posthog-js/react";
|
|||||||
import { IUser } from "@plane/types";
|
import { IUser } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { getUserRole } from "helpers/user.helper";
|
import { getUserRole } from "helpers/user.helper";
|
||||||
|
// constants
|
||||||
|
import { GROUP_WORKSPACE } from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IPosthogWrapper {
|
export interface IPosthogWrapper {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
user: IUser | null;
|
user: IUser | null;
|
||||||
|
currentWorkspaceId: string | undefined;
|
||||||
workspaceRole: number | undefined;
|
workspaceRole: number | undefined;
|
||||||
projectRole: number | undefined;
|
projectRole: number | undefined;
|
||||||
posthogAPIKey: string | null;
|
posthogAPIKey: string | null;
|
||||||
@ -17,7 +20,9 @@ export interface IPosthogWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PostHogProvider: FC<IPosthogWrapper> = (props) => {
|
const PostHogProvider: FC<IPosthogWrapper> = (props) => {
|
||||||
const { children, user, workspaceRole, projectRole, posthogAPIKey, posthogHost } = props;
|
const { children, user, workspaceRole, currentWorkspaceId, projectRole, posthogAPIKey, posthogHost } = props;
|
||||||
|
// states
|
||||||
|
const [lastWorkspaceId, setLastWorkspaceId] = useState(currentWorkspaceId);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -25,10 +30,11 @@ const PostHogProvider: FC<IPosthogWrapper> = (props) => {
|
|||||||
if (user) {
|
if (user) {
|
||||||
// Identify sends an event, so you want may want to limit how often you call it
|
// Identify sends an event, so you want may want to limit how often you call it
|
||||||
posthog?.identify(user.email, {
|
posthog?.identify(user.email, {
|
||||||
email: user.email,
|
id: user.id,
|
||||||
first_name: user.first_name,
|
first_name: user.first_name,
|
||||||
last_name: user.last_name,
|
last_name: user.last_name,
|
||||||
id: user.id,
|
email: user.email,
|
||||||
|
use_case: user.use_case,
|
||||||
workspace_role: workspaceRole ? getUserRole(workspaceRole) : undefined,
|
workspace_role: workspaceRole ? getUserRole(workspaceRole) : undefined,
|
||||||
project_role: projectRole ? getUserRole(projectRole) : undefined,
|
project_role: projectRole ? getUserRole(projectRole) : undefined,
|
||||||
});
|
});
|
||||||
@ -45,6 +51,15 @@ const PostHogProvider: FC<IPosthogWrapper> = (props) => {
|
|||||||
}
|
}
|
||||||
}, [posthogAPIKey, posthogHost]);
|
}, [posthogAPIKey, posthogHost]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Join workspace group on workspace change
|
||||||
|
if (lastWorkspaceId !== currentWorkspaceId && currentWorkspaceId && user) {
|
||||||
|
setLastWorkspaceId(currentWorkspaceId);
|
||||||
|
posthog?.identify(user.email);
|
||||||
|
posthog?.group(GROUP_WORKSPACE, currentWorkspaceId);
|
||||||
|
}
|
||||||
|
}, [currentWorkspaceId, user]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Track page views
|
// Track page views
|
||||||
const handleRouteChange = () => {
|
const handleRouteChange = () => {
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"posthog-js": "^1.88.4",
|
"posthog-js": "^1.105.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-datepicker": "^4.8.0",
|
"react-datepicker": "^4.8.0",
|
||||||
|
@ -6,7 +6,7 @@ import useSWR from "swr";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useUser } from "hooks/store";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// layouts
|
// layouts
|
||||||
@ -60,6 +60,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
|
|||||||
const {
|
const {
|
||||||
commandPalette: { toggleCreatePageModal },
|
commandPalette: { toggleCreatePageModal },
|
||||||
} = useApplication();
|
} = useApplication();
|
||||||
|
const { setTrackElement } = useEventTracker();
|
||||||
|
|
||||||
const { fetchProjectPages, fetchArchivedProjectPages, loader, archivedPageLoader, projectPageIds, archivedPageIds } =
|
const { fetchProjectPages, fetchArchivedProjectPages, loader, archivedPageLoader, projectPageIds, archivedPageIds } =
|
||||||
useProjectPages();
|
useProjectPages();
|
||||||
@ -194,7 +195,10 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => {
|
|||||||
description="Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed issues, lay them out using a library of components, and keep them all in your project’s context. To make short work of any doc, invoke Galileo, Plane’s AI, with a shortcut or the click of a button."
|
description="Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed issues, lay them out using a library of components, and keep them all in your project’s context. To make short work of any doc, invoke Galileo, Plane’s AI, with a shortcut or the click of a button."
|
||||||
primaryButton={{
|
primaryButton={{
|
||||||
text: "Create your first page",
|
text: "Create your first page",
|
||||||
onClick: () => toggleCreatePageModal(true),
|
onClick: () => {
|
||||||
|
setTrackElement("Pages empty state");
|
||||||
|
toggleCreatePageModal(true);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
comicBox={{
|
comicBox={{
|
||||||
title: "A page can be a doc or a doc of docs.",
|
title: "A page can be a doc or a doc of docs.",
|
||||||
|
@ -16,8 +16,11 @@ import { Button } from "@plane/ui";
|
|||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||||
|
// helpers
|
||||||
|
import { getUserRole } from "helpers/user.helper";
|
||||||
// constants
|
// constants
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import { MEMBER_INVITED } from "constants/event-tracker";
|
||||||
|
|
||||||
const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||||
// states
|
// states
|
||||||
@ -43,7 +46,17 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setInviteModal(false);
|
setInviteModal(false);
|
||||||
captureEvent("Member invited", { state: "SUCCESS" });
|
captureEvent(MEMBER_INVITED, {
|
||||||
|
emails: [
|
||||||
|
...data.emails.map((email) => ({
|
||||||
|
email: email.email,
|
||||||
|
role: getUserRole(email.role),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
project_id: undefined,
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace settings member page",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -51,7 +64,17 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
captureEvent("Member invited", { state: "FAILED" });
|
captureEvent(MEMBER_INVITED, {
|
||||||
|
emails: [
|
||||||
|
...data.emails.map((email) => ({
|
||||||
|
email: email.email,
|
||||||
|
role: getUserRole(email.role),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
project_id: undefined,
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Workspace settings member page",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -84,14 +107,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{hasAddMemberPermission && (
|
{hasAddMemberPermission && (
|
||||||
<Button
|
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setTrackElement("WORKSPACE_SETTINGS_MEMBERS_PAGE_HEADER");
|
|
||||||
setInviteModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add member
|
Add member
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -7,6 +7,7 @@ import { AuthService } from "services/auth.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useTimer from "hooks/use-timer";
|
import useTimer from "hooks/use-timer";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/default-layout";
|
import DefaultLayout from "layouts/default-layout";
|
||||||
// components
|
// components
|
||||||
@ -19,6 +20,7 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
|||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// type
|
// type
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
|
import { FORGOT_PASS_LINK } from "constants/event-tracker";
|
||||||
|
|
||||||
type TForgotPasswordFormValues = {
|
type TForgotPasswordFormValues = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -35,6 +37,8 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { email } = router.query;
|
const { email } = router.query;
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast
|
// toast
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// timer
|
// timer
|
||||||
@ -57,6 +61,9 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
email: formData.email,
|
email: formData.email,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
captureEvent(FORGOT_PASS_LINK, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Email sent",
|
title: "Email sent",
|
||||||
@ -65,13 +72,16 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
});
|
});
|
||||||
setResendCodeTimer(30);
|
setResendCodeTimer(30);
|
||||||
})
|
})
|
||||||
.catch((err) =>
|
.catch((err) => {
|
||||||
|
captureEvent(FORGOT_PASS_LINK, {
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,6 +7,7 @@ import { AuthService } from "services/auth.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||||
|
import { useEventTracker } from "hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/default-layout";
|
import DefaultLayout from "layouts/default-layout";
|
||||||
// components
|
// components
|
||||||
@ -21,6 +22,8 @@ import { checkEmailValidity } from "helpers/string.helper";
|
|||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
// icons
|
// icons
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
// constants
|
||||||
|
import { NEW_PASS_CREATED } from "constants/event-tracker";
|
||||||
|
|
||||||
type TResetPasswordFormValues = {
|
type TResetPasswordFormValues = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -41,6 +44,8 @@ const ResetPasswordPage: NextPageWithLayout = () => {
|
|||||||
const { uidb64, token, email } = router.query;
|
const { uidb64, token, email } = router.query;
|
||||||
// states
|
// states
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
// toast
|
// toast
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// sign in redirection hook
|
// sign in redirection hook
|
||||||
@ -66,14 +71,22 @@ const ResetPasswordPage: NextPageWithLayout = () => {
|
|||||||
|
|
||||||
await authService
|
await authService
|
||||||
.resetPassword(uidb64.toString(), token.toString(), payload)
|
.resetPassword(uidb64.toString(), token.toString(), payload)
|
||||||
.then(() => handleRedirection())
|
.then(() => {
|
||||||
.catch((err) =>
|
captureEvent(NEW_PASS_CREATED, {
|
||||||
|
state: "SUCCESS",
|
||||||
|
});
|
||||||
|
handleRedirection();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
captureEvent(NEW_PASS_CREATED, {
|
||||||
|
state: "FAILED",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: err?.error ?? "Something went wrong. Please try again.",
|
message: err?.error ?? "Something went wrong. Please try again.",
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -23,11 +23,13 @@ import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-l
|
|||||||
import emptyInvitation from "public/empty-state/invitation.svg";
|
import emptyInvitation from "public/empty-state/invitation.svg";
|
||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
|
import { getUserRole } from "helpers/user.helper";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
import { MEMBER_ACCEPTED } from "constants/event-tracker";
|
||||||
// components
|
// components
|
||||||
import { EmptyState } from "components/common";
|
import { EmptyState } from "components/common";
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
||||||
const { currentUser, currentUserSettings } = useUser();
|
const { currentUser, currentUserSettings } = useUser();
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -81,11 +83,16 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate("USER_WORKSPACES");
|
mutate("USER_WORKSPACES");
|
||||||
const firstInviteId = invitationsRespond[0];
|
const firstInviteId = invitationsRespond[0];
|
||||||
|
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
||||||
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
||||||
captureEvent("Member accepted", {
|
joinWorkspaceMetricGroup(redirectWorkspace?.id);
|
||||||
...res,
|
captureEvent(MEMBER_ACCEPTED, {
|
||||||
state: "SUCCESS",
|
member_id: invitation?.id,
|
||||||
|
role: getUserRole(invitation?.role!),
|
||||||
|
project_id: undefined,
|
||||||
accepted_from: "App",
|
accepted_from: "App",
|
||||||
|
state: "SUCCESS",
|
||||||
|
element: "Workspace invitations page",
|
||||||
});
|
});
|
||||||
userService
|
userService
|
||||||
.updateUser({ last_workspace_id: redirectWorkspace?.id })
|
.updateUser({ last_workspace_id: redirectWorkspace?.id })
|
||||||
@ -103,6 +110,12 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
captureEvent(MEMBER_ACCEPTED, {
|
||||||
|
project_id: undefined,
|
||||||
|
accepted_from: "App",
|
||||||
|
state: "FAILED",
|
||||||
|
element: "Workspace invitations page",
|
||||||
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -24,6 +24,8 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
|||||||
// types
|
// types
|
||||||
import { IUser, TOnboardingSteps } from "@plane/types";
|
import { IUser, TOnboardingSteps } from "@plane/types";
|
||||||
import { NextPageWithLayout } from "lib/types";
|
import { NextPageWithLayout } from "lib/types";
|
||||||
|
// constants
|
||||||
|
import { USER_ONBOARDING_COMPLETED } from "constants/event-tracker";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
@ -79,7 +81,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
await updateUserOnBoard()
|
await updateUserOnBoard()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
captureEvent("User onboarding completed", {
|
captureEvent(USER_ONBOARDING_COMPLETED, {
|
||||||
user_role: user.role,
|
user_role: user.role,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
|
@ -3,39 +3,49 @@ import posthog from "posthog-js";
|
|||||||
// stores
|
// stores
|
||||||
import { RootStore } from "./root.store";
|
import { RootStore } from "./root.store";
|
||||||
import {
|
import {
|
||||||
EventGroupProps,
|
GROUP_WORKSPACE,
|
||||||
|
WORKSPACE_CREATED,
|
||||||
EventProps,
|
EventProps,
|
||||||
IssueEventProps,
|
IssueEventProps,
|
||||||
getCycleEventPayload,
|
getCycleEventPayload,
|
||||||
getIssueEventPayload,
|
getIssueEventPayload,
|
||||||
getModuleEventPayload,
|
getModuleEventPayload,
|
||||||
getProjectEventPayload,
|
getProjectEventPayload,
|
||||||
|
getProjectStateEventPayload,
|
||||||
|
getWorkspaceEventPayload,
|
||||||
|
getPageEventPayload,
|
||||||
} from "constants/event-tracker";
|
} from "constants/event-tracker";
|
||||||
|
|
||||||
export interface IEventTrackerStore {
|
export interface IEventTrackerStore {
|
||||||
// properties
|
// properties
|
||||||
trackElement: string;
|
trackElement: string | undefined;
|
||||||
// computed
|
// computed
|
||||||
getRequiredPayload: any;
|
getRequiredProperties: any;
|
||||||
// actions
|
// actions
|
||||||
|
resetSession: () => void;
|
||||||
setTrackElement: (element: string) => void;
|
setTrackElement: (element: string) => void;
|
||||||
captureEvent: (eventName: string, payload: object | [] | null, group?: EventGroupProps) => void;
|
captureEvent: (eventName: string, payload?: any) => void;
|
||||||
|
joinWorkspaceMetricGroup: (workspaceId?: string) => void;
|
||||||
|
captureWorkspaceEvent: (props: EventProps) => void;
|
||||||
captureProjectEvent: (props: EventProps) => void;
|
captureProjectEvent: (props: EventProps) => void;
|
||||||
captureCycleEvent: (props: EventProps) => void;
|
captureCycleEvent: (props: EventProps) => void;
|
||||||
captureModuleEvent: (props: EventProps) => void;
|
captureModuleEvent: (props: EventProps) => void;
|
||||||
|
capturePageEvent: (props: EventProps) => void;
|
||||||
captureIssueEvent: (props: IssueEventProps) => void;
|
captureIssueEvent: (props: IssueEventProps) => void;
|
||||||
|
captureProjectStateEvent: (props: EventProps) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventTrackerStore implements IEventTrackerStore {
|
export class EventTrackerStore implements IEventTrackerStore {
|
||||||
trackElement: string = "";
|
trackElement: string | undefined = undefined;
|
||||||
rootStore;
|
rootStore;
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// properties
|
// properties
|
||||||
trackElement: observable,
|
trackElement: observable,
|
||||||
// computed
|
// computed
|
||||||
getRequiredPayload: computed,
|
getRequiredProperties: computed,
|
||||||
// actions
|
// actions
|
||||||
|
resetSession: action,
|
||||||
setTrackElement: action,
|
setTrackElement: action,
|
||||||
captureEvent: action,
|
captureEvent: action,
|
||||||
captureProjectEvent: action,
|
captureProjectEvent: action,
|
||||||
@ -48,12 +58,12 @@ export class EventTrackerStore implements IEventTrackerStore {
|
|||||||
/**
|
/**
|
||||||
* @description: Returns the necessary property for the event tracking
|
* @description: Returns the necessary property for the event tracking
|
||||||
*/
|
*/
|
||||||
get getRequiredPayload() {
|
get getRequiredProperties() {
|
||||||
const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace;
|
const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace;
|
||||||
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
|
const currentProjectDetails = this.rootStore.projectRoot.project.currentProjectDetails;
|
||||||
return {
|
return {
|
||||||
workspace_id: currentWorkspaceDetails?.id ?? "",
|
workspace_id: currentWorkspaceDetails?.id,
|
||||||
project_id: currentProjectDetails?.id ?? "",
|
project_id: currentProjectDetails?.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,42 +71,74 @@ export class EventTrackerStore implements IEventTrackerStore {
|
|||||||
* @description: Set the trigger point of event.
|
* @description: Set the trigger point of event.
|
||||||
* @param {string} element
|
* @param {string} element
|
||||||
*/
|
*/
|
||||||
setTrackElement = (element: string) => {
|
setTrackElement = (element?: string) => {
|
||||||
this.trackElement = element;
|
this.trackElement = element;
|
||||||
};
|
};
|
||||||
|
|
||||||
postHogGroup = (group: EventGroupProps) => {
|
/**
|
||||||
if (group && group!.isGrouping === true) {
|
* @description: Reset the session.
|
||||||
posthog?.group(group!.groupType!, group!.groupId!, {
|
*/
|
||||||
date: new Date(),
|
resetSession = () => {
|
||||||
workspace_id: group!.groupId,
|
posthog?.reset();
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
captureEvent = (eventName: string, payload: object | [] | null) => {
|
/**
|
||||||
posthog?.capture(eventName, {
|
* @description: Creates the workspace metric group.
|
||||||
...payload,
|
* @param {string} userEmail
|
||||||
element: this.trackElement ?? "",
|
* @param {string} workspaceId
|
||||||
|
*/
|
||||||
|
joinWorkspaceMetricGroup = (workspaceId?: string) => {
|
||||||
|
if (!workspaceId) return;
|
||||||
|
posthog?.group(GROUP_WORKSPACE, workspaceId, {
|
||||||
|
date: new Date().toDateString(),
|
||||||
|
workspace_id: workspaceId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Captures the event.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {any} payload
|
||||||
|
*/
|
||||||
|
captureEvent = (eventName: string, payload?: any) => {
|
||||||
|
posthog?.capture(eventName, {
|
||||||
|
...this.getRequiredProperties,
|
||||||
|
...payload,
|
||||||
|
element: payload?.element ?? this.trackElement,
|
||||||
|
});
|
||||||
|
this.setTrackElement(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Captures the workspace crud related events.
|
||||||
|
* @param {EventProps} props
|
||||||
|
*/
|
||||||
|
captureWorkspaceEvent = (props: EventProps) => {
|
||||||
|
const { eventName, payload } = props;
|
||||||
|
if (eventName === WORKSPACE_CREATED && payload.state == "SUCCESS") {
|
||||||
|
this.joinWorkspaceMetricGroup(payload.id);
|
||||||
|
}
|
||||||
|
const eventPayload: any = getWorkspaceEventPayload({
|
||||||
|
...payload,
|
||||||
|
element: payload.element ?? this.trackElement,
|
||||||
|
});
|
||||||
|
posthog?.capture(eventName, eventPayload);
|
||||||
|
this.setTrackElement(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: Captures the project related events.
|
* @description: Captures the project related events.
|
||||||
* @param {EventProps} props
|
* @param {EventProps} props
|
||||||
*/
|
*/
|
||||||
captureProjectEvent = (props: EventProps) => {
|
captureProjectEvent = (props: EventProps) => {
|
||||||
const { eventName, payload, group } = props;
|
const { eventName, payload } = props;
|
||||||
if (group) {
|
|
||||||
this.postHogGroup(group);
|
|
||||||
}
|
|
||||||
const eventPayload: any = getProjectEventPayload({
|
const eventPayload: any = getProjectEventPayload({
|
||||||
...this.getRequiredPayload,
|
...this.getRequiredProperties,
|
||||||
...payload,
|
...payload,
|
||||||
element: payload.element ?? this.trackElement,
|
element: payload.element ?? this.trackElement,
|
||||||
});
|
});
|
||||||
posthog?.capture(eventName, eventPayload);
|
posthog?.capture(eventName, eventPayload);
|
||||||
this.setTrackElement("");
|
this.setTrackElement(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,17 +146,14 @@ export class EventTrackerStore implements IEventTrackerStore {
|
|||||||
* @param {EventProps} props
|
* @param {EventProps} props
|
||||||
*/
|
*/
|
||||||
captureCycleEvent = (props: EventProps) => {
|
captureCycleEvent = (props: EventProps) => {
|
||||||
const { eventName, payload, group } = props;
|
const { eventName, payload } = props;
|
||||||
if (group) {
|
|
||||||
this.postHogGroup(group);
|
|
||||||
}
|
|
||||||
const eventPayload: any = getCycleEventPayload({
|
const eventPayload: any = getCycleEventPayload({
|
||||||
...this.getRequiredPayload,
|
...this.getRequiredProperties,
|
||||||
...payload,
|
...payload,
|
||||||
element: payload.element ?? this.trackElement,
|
element: payload.element ?? this.trackElement,
|
||||||
});
|
});
|
||||||
posthog?.capture(eventName, eventPayload);
|
posthog?.capture(eventName, eventPayload);
|
||||||
this.setTrackElement("");
|
this.setTrackElement(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,17 +161,29 @@ export class EventTrackerStore implements IEventTrackerStore {
|
|||||||
* @param {EventProps} props
|
* @param {EventProps} props
|
||||||
*/
|
*/
|
||||||
captureModuleEvent = (props: EventProps) => {
|
captureModuleEvent = (props: EventProps) => {
|
||||||
const { eventName, payload, group } = props;
|
const { eventName, payload } = props;
|
||||||
if (group) {
|
|
||||||
this.postHogGroup(group);
|
|
||||||
}
|
|
||||||
const eventPayload: any = getModuleEventPayload({
|
const eventPayload: any = getModuleEventPayload({
|
||||||
...this.getRequiredPayload,
|
...this.getRequiredProperties,
|
||||||
...payload,
|
...payload,
|
||||||
element: payload.element ?? this.trackElement,
|
element: payload.element ?? this.trackElement,
|
||||||
});
|
});
|
||||||
posthog?.capture(eventName, eventPayload);
|
posthog?.capture(eventName, eventPayload);
|
||||||
this.setTrackElement("");
|
this.setTrackElement(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Captures the project pages related events.
|
||||||
|
* @param {EventProps} props
|
||||||
|
*/
|
||||||
|
capturePageEvent = (props: EventProps) => {
|
||||||
|
const { eventName, payload } = props;
|
||||||
|
const eventPayload: any = getPageEventPayload({
|
||||||
|
...this.getRequiredProperties,
|
||||||
|
...payload,
|
||||||
|
element: payload.element ?? this.trackElement,
|
||||||
|
});
|
||||||
|
posthog?.capture(eventName, eventPayload);
|
||||||
|
this.setTrackElement(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,16 +191,29 @@ export class EventTrackerStore implements IEventTrackerStore {
|
|||||||
* @param {IssueEventProps} props
|
* @param {IssueEventProps} props
|
||||||
*/
|
*/
|
||||||
captureIssueEvent = (props: IssueEventProps) => {
|
captureIssueEvent = (props: IssueEventProps) => {
|
||||||
const { eventName, payload, group } = props;
|
const { eventName, payload } = props;
|
||||||
if (group) {
|
|
||||||
this.postHogGroup(group);
|
|
||||||
}
|
|
||||||
const eventPayload: any = {
|
const eventPayload: any = {
|
||||||
...getIssueEventPayload(props),
|
...getIssueEventPayload(props),
|
||||||
...this.getRequiredPayload,
|
...this.getRequiredProperties,
|
||||||
state_group: this.rootStore.state.getStateById(payload.state_id)?.group ?? "",
|
state_group: this.rootStore.state.getStateById(payload.state_id)?.group ?? "",
|
||||||
element: payload.element ?? this.trackElement,
|
element: payload.element ?? this.trackElement,
|
||||||
};
|
};
|
||||||
posthog?.capture(eventName, eventPayload);
|
posthog?.capture(eventName, eventPayload);
|
||||||
|
this.setTrackElement(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Captures the issue related events.
|
||||||
|
* @param {IssueEventProps} props
|
||||||
|
*/
|
||||||
|
captureProjectStateEvent = (props: EventProps) => {
|
||||||
|
const { eventName, payload } = props;
|
||||||
|
const eventPayload: any = getProjectStateEventPayload({
|
||||||
|
...this.getRequiredProperties,
|
||||||
|
...payload,
|
||||||
|
element: payload.element ?? this.trackElement,
|
||||||
|
});
|
||||||
|
posthog?.capture(eventName, eventPayload);
|
||||||
|
this.setTrackElement(undefined);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import sortBy from "lodash/sortBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import indexOf from "lodash/indexOf";
|
import indexOf from "lodash/indexOf";
|
||||||
import reverse from "lodash/reverse";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import values from "lodash/values";
|
import values from "lodash/values";
|
||||||
// types
|
// types
|
||||||
import { TIssue, TIssueMap, TIssueGroupByOptions, TIssueOrderByOptions } from "@plane/types";
|
import { TIssue, TIssueMap, TIssueGroupByOptions, TIssueOrderByOptions } from "@plane/types";
|
||||||
@ -144,98 +144,189 @@ export class IssueHelperStore implements TIssueHelperStore {
|
|||||||
issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => {
|
issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => {
|
||||||
switch (groupBy) {
|
switch (groupBy) {
|
||||||
case "state":
|
case "state":
|
||||||
return this.rootStore?.states || [];
|
return Object.keys(this.rootStore?.stateMap || {});
|
||||||
case "state_detail.group":
|
case "state_detail.group":
|
||||||
return Object.keys(STATE_GROUPS);
|
return Object.keys(STATE_GROUPS);
|
||||||
case "priority":
|
case "priority":
|
||||||
return ISSUE_PRIORITIES.map((i) => i.key);
|
return ISSUE_PRIORITIES.map((i) => i.key);
|
||||||
case "labels":
|
case "labels":
|
||||||
return this.rootStore?.labels || [];
|
return Object.keys(this.rootStore?.labelMap || {});
|
||||||
case "created_by":
|
case "created_by":
|
||||||
return this.rootStore?.members || [];
|
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {});
|
||||||
case "assignees":
|
case "assignees":
|
||||||
return this.rootStore?.members || [];
|
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {});
|
||||||
case "project":
|
case "project":
|
||||||
return this.rootStore?.projects || [];
|
return Object.keys(this.rootStore?.projectMap || {});
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Method is used to get data of the issue based on the ids of the data for states, labels adn assignees
|
||||||
|
* @param dataType what type of data is being sent
|
||||||
|
* @param dataIds id/ids of the data that is to be populated
|
||||||
|
* @param order ascending or descending for arrays of data
|
||||||
|
* @returns string | string[] of sortable fields to be used for sorting
|
||||||
|
*/
|
||||||
|
populateIssueDataForSorting(
|
||||||
|
dataType: "state_id" | "label_ids" | "assignee_ids",
|
||||||
|
dataIds: string | string[] | null | undefined,
|
||||||
|
order?: "asc" | "desc"
|
||||||
|
) {
|
||||||
|
if (!dataIds) return;
|
||||||
|
|
||||||
|
const dataValues: string[] = [];
|
||||||
|
const isDataIdsArray = Array.isArray(dataIds);
|
||||||
|
const dataIdsArray = isDataIdsArray ? dataIds : [dataIds];
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case "state_id":
|
||||||
|
const stateMap = this.rootStore?.stateMap;
|
||||||
|
if (!stateMap) break;
|
||||||
|
for (const dataId of dataIdsArray) {
|
||||||
|
const state = stateMap[dataId];
|
||||||
|
if (state && state.name) dataValues.push(state.name.toLocaleLowerCase());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "label_ids":
|
||||||
|
const labelMap = this.rootStore?.labelMap;
|
||||||
|
if (!labelMap) break;
|
||||||
|
for (const dataId of dataIdsArray) {
|
||||||
|
const label = labelMap[dataId];
|
||||||
|
if (label && label.name) dataValues.push(label.name.toLocaleLowerCase());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "assignee_ids":
|
||||||
|
const memberMap = this.rootStore?.memberMap;
|
||||||
|
if (!memberMap) break;
|
||||||
|
for (const dataId of dataIdsArray) {
|
||||||
|
const member = memberMap[dataId];
|
||||||
|
if (memberMap && member.first_name) dataValues.push(member.first_name.toLocaleLowerCase());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDataIdsArray ? (order ? orderBy(dataValues, undefined, [order]) : dataValues) : dataValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Method is mainly used to filter out empty values in the begining
|
||||||
|
* @param key key of the value that is to be checked if empty
|
||||||
|
* @param object any object in which the key's value is to be checked
|
||||||
|
* @returns 1 if emoty, 0 if not empty
|
||||||
|
*/
|
||||||
|
getSortOrderToFilterEmptyValues(key: string, object: any) {
|
||||||
|
const value = object?.[key];
|
||||||
|
|
||||||
|
if (typeof value !== "number" && isEmpty(value)) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
issuesSortWithOrderBy = (issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[] => {
|
issuesSortWithOrderBy = (issueObject: TIssueMap, key: Partial<TIssueOrderByOptions>): TIssue[] => {
|
||||||
let array = values(issueObject);
|
let array = values(issueObject);
|
||||||
array = reverse(sortBy(array, "created_at"));
|
array = orderBy(array, "created_at");
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "sort_order":
|
case "sort_order":
|
||||||
return sortBy(array, "sort_order");
|
return orderBy(array, "sort_order");
|
||||||
|
|
||||||
case "state__name":
|
case "state__name":
|
||||||
return reverse(sortBy(array, "state"));
|
return orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue["state_id"]));
|
||||||
case "-state__name":
|
case "-state__name":
|
||||||
return sortBy(array, "state");
|
return orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue["state_id"]), ["desc"]);
|
||||||
|
|
||||||
// dates
|
// dates
|
||||||
case "created_at":
|
case "created_at":
|
||||||
return sortBy(array, "created_at");
|
return orderBy(array, "created_at");
|
||||||
case "-created_at":
|
case "-created_at":
|
||||||
return reverse(sortBy(array, "created_at"));
|
return orderBy(array, "created_at", ["desc"]);
|
||||||
|
|
||||||
case "updated_at":
|
case "updated_at":
|
||||||
return sortBy(array, "updated_at");
|
return orderBy(array, "updated_at");
|
||||||
case "-updated_at":
|
case "-updated_at":
|
||||||
return reverse(sortBy(array, "updated_at"));
|
return orderBy(array, "updated_at", ["desc"]);
|
||||||
|
|
||||||
case "start_date":
|
case "start_date":
|
||||||
return sortBy(array, "start_date");
|
return orderBy(array, [this.getSortOrderToFilterEmptyValues.bind(null, "start_date"), "start_date"]); //preferring sorting based on empty values to always keep the empty values below
|
||||||
case "-start_date":
|
case "-start_date":
|
||||||
return reverse(sortBy(array, "start_date"));
|
return orderBy(
|
||||||
|
array,
|
||||||
|
[this.getSortOrderToFilterEmptyValues.bind(null, "start_date"), "start_date"], //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
["asc", "desc"]
|
||||||
|
);
|
||||||
|
|
||||||
case "target_date":
|
case "target_date":
|
||||||
return sortBy(array, "target_date");
|
return orderBy(array, [this.getSortOrderToFilterEmptyValues.bind(null, "target_date"), "target_date"]); //preferring sorting based on empty values to always keep the empty values below
|
||||||
case "-target_date":
|
case "-target_date":
|
||||||
return reverse(sortBy(array, "target_date"));
|
return orderBy(
|
||||||
|
array,
|
||||||
|
[this.getSortOrderToFilterEmptyValues.bind(null, "target_date"), "target_date"], //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
["asc", "desc"]
|
||||||
|
);
|
||||||
|
|
||||||
// custom
|
// custom
|
||||||
case "priority": {
|
case "priority": {
|
||||||
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
||||||
return reverse(sortBy(array, (_issue: TIssue) => indexOf(sortArray, _issue.priority)));
|
return orderBy(array, (_issue: TIssue) => indexOf(sortArray, _issue.priority), ["desc"]);
|
||||||
}
|
}
|
||||||
case "-priority": {
|
case "-priority": {
|
||||||
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
||||||
return sortBy(array, (_issue: TIssue) => indexOf(sortArray, _issue.priority));
|
return orderBy(array, (_issue: TIssue) => indexOf(sortArray, _issue.priority));
|
||||||
}
|
}
|
||||||
|
|
||||||
// number
|
// number
|
||||||
case "attachment_count":
|
case "attachment_count":
|
||||||
return sortBy(array, "attachment_count");
|
return orderBy(array, "attachment_count");
|
||||||
case "-attachment_count":
|
case "-attachment_count":
|
||||||
return reverse(sortBy(array, "attachment_count"));
|
return orderBy(array, "attachment_count", ["desc"]);
|
||||||
|
|
||||||
case "estimate_point":
|
case "estimate_point":
|
||||||
return sortBy(array, "estimate_point");
|
return orderBy(array, [this.getSortOrderToFilterEmptyValues.bind(null, "estimate_point"), "estimate_point"]); //preferring sorting based on empty values to always keep the empty values below
|
||||||
case "-estimate_point":
|
case "-estimate_point":
|
||||||
return reverse(sortBy(array, "estimate_point"));
|
return orderBy(
|
||||||
|
array,
|
||||||
|
[this.getSortOrderToFilterEmptyValues.bind(null, "estimate_point"), "estimate_point"], //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
["asc", "desc"]
|
||||||
|
);
|
||||||
|
|
||||||
case "link_count":
|
case "link_count":
|
||||||
return sortBy(array, "link_count");
|
return orderBy(array, "link_count");
|
||||||
case "-link_count":
|
case "-link_count":
|
||||||
return reverse(sortBy(array, "link_count"));
|
return orderBy(array, "link_count", ["desc"]);
|
||||||
|
|
||||||
case "sub_issues_count":
|
case "sub_issues_count":
|
||||||
return sortBy(array, "sub_issues_count");
|
return orderBy(array, "sub_issues_count");
|
||||||
case "-sub_issues_count":
|
case "-sub_issues_count":
|
||||||
return reverse(sortBy(array, "sub_issues_count"));
|
return orderBy(array, "sub_issues_count", ["desc"]);
|
||||||
|
|
||||||
// Array
|
// Array
|
||||||
case "labels__name":
|
case "labels__name":
|
||||||
return reverse(sortBy(array, "labels"));
|
return orderBy(array, [
|
||||||
|
this.getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
(issue) => this.populateIssueDataForSorting("label_ids", issue["label_ids"], "asc"),
|
||||||
|
]);
|
||||||
case "-labels__name":
|
case "-labels__name":
|
||||||
return sortBy(array, "labels");
|
return orderBy(
|
||||||
|
array,
|
||||||
|
[
|
||||||
|
this.getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
(issue) => this.populateIssueDataForSorting("label_ids", issue["label_ids"], "desc"),
|
||||||
|
],
|
||||||
|
["asc", "desc"]
|
||||||
|
);
|
||||||
|
|
||||||
case "assignees__first_name":
|
case "assignees__first_name":
|
||||||
return reverse(sortBy(array, "assignees"));
|
return orderBy(array, [
|
||||||
|
this.getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
(issue) => this.populateIssueDataForSorting("assignee_ids", issue["assignee_ids"], "asc"),
|
||||||
|
]);
|
||||||
case "-assignees__first_name":
|
case "-assignees__first_name":
|
||||||
return sortBy(array, "assignees");
|
return orderBy(
|
||||||
|
array,
|
||||||
|
[
|
||||||
|
this.getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||||
|
(issue) => this.populateIssueDataForSorting("assignee_ids", issue["assignee_ids"], "desc"),
|
||||||
|
],
|
||||||
|
["asc", "desc"]
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return array;
|
return array;
|
||||||
|
@ -4,7 +4,7 @@ import isEmpty from "lodash/isEmpty";
|
|||||||
import { RootStore } from "../root.store";
|
import { RootStore } from "../root.store";
|
||||||
import { IStateStore, StateStore } from "../state.store";
|
import { IStateStore, StateStore } from "../state.store";
|
||||||
// issues data store
|
// issues data store
|
||||||
import { IState } from "@plane/types";
|
import { IIssueLabel, IProject, IState, IUserLite } from "@plane/types";
|
||||||
import { IIssueStore, IssueStore } from "./issue.store";
|
import { IIssueStore, IssueStore } from "./issue.store";
|
||||||
import { IIssueDetail, IssueDetail } from "./issue-details/root.store";
|
import { IIssueDetail, IssueDetail } from "./issue-details/root.store";
|
||||||
import { IWorkspaceIssuesFilter, WorkspaceIssuesFilter, IWorkspaceIssues, WorkspaceIssues } from "./workspace";
|
import { IWorkspaceIssuesFilter, WorkspaceIssuesFilter, IWorkspaceIssues, WorkspaceIssues } from "./workspace";
|
||||||
@ -22,6 +22,7 @@ import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedI
|
|||||||
import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft";
|
import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft";
|
||||||
import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store";
|
import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store";
|
||||||
import { ICalendarStore, CalendarStore } from "./issue_calendar_view.store";
|
import { ICalendarStore, CalendarStore } from "./issue_calendar_view.store";
|
||||||
|
import { IWorkspaceMembership } from "store/member/workspace-member.store";
|
||||||
|
|
||||||
export interface IIssueRootStore {
|
export interface IIssueRootStore {
|
||||||
currentUserId: string | undefined;
|
currentUserId: string | undefined;
|
||||||
@ -32,11 +33,12 @@ export interface IIssueRootStore {
|
|||||||
viewId: string | undefined;
|
viewId: string | undefined;
|
||||||
globalViewId: string | undefined; // all issues view id
|
globalViewId: string | undefined; // all issues view id
|
||||||
userId: string | undefined; // user profile detail Id
|
userId: string | undefined; // user profile detail Id
|
||||||
states: string[] | undefined;
|
stateMap: Record<string, IState> | undefined;
|
||||||
stateDetails: IState[] | undefined;
|
stateDetails: IState[] | undefined;
|
||||||
labels: string[] | undefined;
|
labelMap: Record<string, IIssueLabel> | undefined;
|
||||||
members: string[] | undefined;
|
workSpaceMemberRolesMap: Record<string, IWorkspaceMembership> | undefined;
|
||||||
projects: string[] | undefined;
|
memberMap: Record<string, IUserLite> | undefined;
|
||||||
|
projectMap: Record<string, IProject> | undefined;
|
||||||
|
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
|
|
||||||
@ -83,11 +85,12 @@ export class IssueRootStore implements IIssueRootStore {
|
|||||||
viewId: string | undefined = undefined;
|
viewId: string | undefined = undefined;
|
||||||
globalViewId: string | undefined = undefined;
|
globalViewId: string | undefined = undefined;
|
||||||
userId: string | undefined = undefined;
|
userId: string | undefined = undefined;
|
||||||
states: string[] | undefined = undefined;
|
stateMap: Record<string, IState> | undefined = undefined;
|
||||||
stateDetails: IState[] | undefined = undefined;
|
stateDetails: IState[] | undefined = undefined;
|
||||||
labels: string[] | undefined = undefined;
|
labelMap: Record<string, IIssueLabel> | undefined = undefined;
|
||||||
members: string[] | undefined = undefined;
|
workSpaceMemberRolesMap: Record<string, IWorkspaceMembership> | undefined = undefined;
|
||||||
projects: string[] | undefined = undefined;
|
memberMap: Record<string, IUserLite> | undefined = undefined;
|
||||||
|
projectMap: Record<string, IProject> | undefined = undefined;
|
||||||
|
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
|
|
||||||
@ -133,11 +136,12 @@ export class IssueRootStore implements IIssueRootStore {
|
|||||||
viewId: observable.ref,
|
viewId: observable.ref,
|
||||||
userId: observable.ref,
|
userId: observable.ref,
|
||||||
globalViewId: observable.ref,
|
globalViewId: observable.ref,
|
||||||
states: observable,
|
stateMap: observable,
|
||||||
stateDetails: observable,
|
stateDetails: observable,
|
||||||
labels: observable,
|
labelMap: observable,
|
||||||
members: observable,
|
memberMap: observable,
|
||||||
projects: observable,
|
workSpaceMemberRolesMap: observable,
|
||||||
|
projectMap: observable,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = rootStore;
|
this.rootStore = rootStore;
|
||||||
@ -151,13 +155,14 @@ export class IssueRootStore implements IIssueRootStore {
|
|||||||
if (rootStore.app.router.viewId) this.viewId = rootStore.app.router.viewId;
|
if (rootStore.app.router.viewId) this.viewId = rootStore.app.router.viewId;
|
||||||
if (rootStore.app.router.globalViewId) this.globalViewId = rootStore.app.router.globalViewId;
|
if (rootStore.app.router.globalViewId) this.globalViewId = rootStore.app.router.globalViewId;
|
||||||
if (rootStore.app.router.userId) this.userId = rootStore.app.router.userId;
|
if (rootStore.app.router.userId) this.userId = rootStore.app.router.userId;
|
||||||
if (!isEmpty(rootStore?.state?.stateMap)) this.states = Object.keys(rootStore?.state?.stateMap);
|
if (!isEmpty(rootStore?.state?.stateMap)) this.stateMap = rootStore?.state?.stateMap;
|
||||||
if (!isEmpty(rootStore?.state?.projectStates)) this.stateDetails = rootStore?.state?.projectStates;
|
if (!isEmpty(rootStore?.state?.projectStates)) this.stateDetails = rootStore?.state?.projectStates;
|
||||||
if (!isEmpty(rootStore?.label?.labelMap)) this.labels = Object.keys(rootStore?.label?.labelMap);
|
if (!isEmpty(rootStore?.label?.labelMap)) this.labelMap = rootStore?.label?.labelMap;
|
||||||
if (!isEmpty(rootStore?.memberRoot?.workspace?.workspaceMemberMap))
|
if (!isEmpty(rootStore?.memberRoot?.workspace?.workspaceMemberMap))
|
||||||
this.members = Object.keys(rootStore?.memberRoot?.workspace?.workspaceMemberMap);
|
this.workSpaceMemberRolesMap = rootStore?.memberRoot?.workspace?.memberMap || undefined;
|
||||||
|
if (!isEmpty(rootStore?.memberRoot?.memberMap)) this.memberMap = rootStore?.memberRoot?.memberMap || undefined;
|
||||||
if (!isEmpty(rootStore?.projectRoot?.project?.projectMap))
|
if (!isEmpty(rootStore?.projectRoot?.project?.projectMap))
|
||||||
this.projects = Object.keys(rootStore?.projectRoot?.project?.projectMap);
|
this.projectMap = rootStore?.projectRoot?.project?.projectMap;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.issues = new IssueStore();
|
this.issues = new IssueStore();
|
||||||
|
@ -26,6 +26,7 @@ export interface IWorkspaceMemberStore {
|
|||||||
// computed
|
// computed
|
||||||
workspaceMemberIds: string[] | null;
|
workspaceMemberIds: string[] | null;
|
||||||
workspaceMemberInvitationIds: string[] | null;
|
workspaceMemberInvitationIds: string[] | null;
|
||||||
|
memberMap: Record<string, IWorkspaceMembership> | null;
|
||||||
// computed actions
|
// computed actions
|
||||||
getSearchedWorkspaceMemberIds: (searchQuery: string) => string[] | null;
|
getSearchedWorkspaceMemberIds: (searchQuery: string) => string[] | null;
|
||||||
getSearchedWorkspaceInvitationIds: (searchQuery: string) => string[] | null;
|
getSearchedWorkspaceInvitationIds: (searchQuery: string) => string[] | null;
|
||||||
@ -68,6 +69,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
|||||||
// computed
|
// computed
|
||||||
workspaceMemberIds: computed,
|
workspaceMemberIds: computed,
|
||||||
workspaceMemberInvitationIds: computed,
|
workspaceMemberInvitationIds: computed,
|
||||||
|
memberMap: computed,
|
||||||
// actions
|
// actions
|
||||||
fetchWorkspaceMembers: action,
|
fetchWorkspaceMembers: action,
|
||||||
updateMember: action,
|
updateMember: action,
|
||||||
@ -100,6 +102,12 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
|||||||
return memberIds;
|
return memberIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get memberMap() {
|
||||||
|
const workspaceSlug = this.routerStore.workspaceSlug;
|
||||||
|
if (!workspaceSlug) return null;
|
||||||
|
return this.workspaceMemberMap?.[workspaceSlug] ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
get workspaceMemberInvitationIds() {
|
get workspaceMemberInvitationIds() {
|
||||||
const workspaceSlug = this.routerStore.workspaceSlug;
|
const workspaceSlug = this.routerStore.workspaceSlug;
|
||||||
if (!workspaceSlug) return null;
|
if (!workspaceSlug) return null;
|
||||||
|
@ -250,6 +250,7 @@ export class UserRootStore implements IUserRootStore {
|
|||||||
this.isUserLoggedIn = false;
|
this.isUserLoggedIn = false;
|
||||||
});
|
});
|
||||||
this.membership = new UserMembershipStore(this.rootStore);
|
this.membership = new UserMembershipStore(this.rootStore);
|
||||||
|
this.rootStore.eventTracker.resetSession();
|
||||||
this.rootStore.resetOnSignout();
|
this.rootStore.resetOnSignout();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -264,6 +265,7 @@ export class UserRootStore implements IUserRootStore {
|
|||||||
this.isUserLoggedIn = false;
|
this.isUserLoggedIn = false;
|
||||||
});
|
});
|
||||||
this.membership = new UserMembershipStore(this.rootStore);
|
this.membership = new UserMembershipStore(this.rootStore);
|
||||||
|
this.rootStore.eventTracker.resetSession();
|
||||||
this.rootStore.resetOnSignout();
|
this.rootStore.resetOnSignout();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user