mirror of
https://github.com/makeplane/plane.git
synced 2025-12-22 22:59:33 +01:00
feat: integrate enhanced new set of event trackers
This commit is contained in:
@@ -18,6 +18,9 @@ import { useProject } from "@/hooks/store/use-project";
|
|||||||
// plane web imports
|
// plane web imports
|
||||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
||||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
import { getUserRoleString, trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
export const PagesListHeader = observer(function PagesListHeader() {
|
export const PagesListHeader = observer(function PagesListHeader() {
|
||||||
// states
|
// states
|
||||||
@@ -28,6 +31,9 @@ export const PagesListHeader = observer(function PagesListHeader() {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const pageType = searchParams.get("type");
|
const pageType = searchParams.get("type");
|
||||||
// store hooks
|
// store hooks
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { currentProjectDetails, loader } = useProject();
|
const { currentProjectDetails, loader } = useProject();
|
||||||
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
|
const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
|
||||||
// handle page create
|
// handle page create
|
||||||
@@ -40,13 +46,16 @@ export const PagesListHeader = observer(function PagesListHeader() {
|
|||||||
|
|
||||||
await createPage(payload)
|
await createPage(payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser) {
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackPageCreated(
|
||||||
id: res?.id,
|
{ id: res?.id ?? "", created_at: new Date().toISOString() },
|
||||||
state: "SUCCESS",
|
currentWorkspace,
|
||||||
},
|
currentUser,
|
||||||
});
|
"project",
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
|
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
|
||||||
router.push(pageId);
|
router.push(pageId);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
|||||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||||
// plane web services
|
// plane web services
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
|
import { joinWorkspaceGroup } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
|
|
||||||
@@ -80,10 +81,9 @@ function UserInvitationsPage() {
|
|||||||
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
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;
|
||||||
if (redirectWorkspace?.id) {
|
if (redirectWorkspace?.id) {
|
||||||
joinEventGroup(GROUP_WORKSPACE_TRACKER_EVENT, redirectWorkspace?.id, {
|
if (redirectWorkspace) {
|
||||||
date: new Date().toDateString(),
|
joinWorkspaceGroup(redirectWorkspace);
|
||||||
workspace_id: redirectWorkspace?.id,
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
captureSuccess({
|
captureSuccess({
|
||||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
|||||||
import type { TProject } from "@/plane-web/types/projects";
|
import type { TProject } from "@/plane-web/types/projects";
|
||||||
import ProjectAttributes from "./attributes";
|
import ProjectAttributes from "./attributes";
|
||||||
import { getProjectFormValues } from "./utils";
|
import { getProjectFormValues } from "./utils";
|
||||||
|
import { getUserRoleString, trackProjectCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
|
||||||
export type TCreateProjectFormProps = {
|
export type TCreateProjectFormProps = {
|
||||||
setToFavorite?: boolean;
|
setToFavorite?: boolean;
|
||||||
@@ -36,6 +39,9 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
|
|||||||
// store
|
// store
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addProjectToFavorites, createProject, updateProject } = useProject();
|
const { addProjectToFavorites, createProject, updateProject } = useProject();
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
// states
|
// states
|
||||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||||
// form info
|
// form info
|
||||||
@@ -98,12 +104,15 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
|
|||||||
await updateCoverImageStatus(res.id, coverImage);
|
await updateCoverImageStatus(res.id, coverImage);
|
||||||
await updateProject(workspaceSlug.toString(), res.id, { cover_image_url: coverImage });
|
await updateProject(workspaceSlug.toString(), res.id, { cover_image_url: coverImage });
|
||||||
}
|
}
|
||||||
captureSuccess({
|
if (currentUser && currentWorkspace && res) {
|
||||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackProjectCreated(
|
||||||
identifier: formData.identifier,
|
{ id: res.id, created_at: res.created_at instanceof Date ? res.created_at : new Date() },
|
||||||
},
|
currentWorkspace,
|
||||||
});
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: t("success"),
|
title: t("success"),
|
||||||
|
|||||||
266
apps/web/ce/helpers/event-tracker-v2.helper.ts
Normal file
266
apps/web/ce/helpers/event-tracker-v2.helper.ts
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import posthog from "posthog-js";
|
||||||
|
import { EUserPermissions } from "@plane/types";
|
||||||
|
import type { EUserProjectRoles, EUserWorkspaceRoles, IUser, IWorkspace, TUserProfile } from "@plane/types";
|
||||||
|
|
||||||
|
type TUserRole = "guest" | "member" | "admin" | "unknown";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* Utilities
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user role string from the user role enum
|
||||||
|
* @param role - The user role enum
|
||||||
|
* @returns The user role string
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getUserRoleString = (
|
||||||
|
role: EUserPermissions | EUserWorkspaceRoles | EUserProjectRoles | undefined
|
||||||
|
): TUserRole => {
|
||||||
|
if (!role) return "unknown";
|
||||||
|
switch (role) {
|
||||||
|
case EUserPermissions.GUEST:
|
||||||
|
return "guest";
|
||||||
|
case EUserPermissions.MEMBER:
|
||||||
|
return "member";
|
||||||
|
case EUserPermissions.ADMIN:
|
||||||
|
return "admin";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* USER IDENTIFICATION
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify a user in PostHog with all required person properties
|
||||||
|
* Call this after signup, login, or whenever session becomes authenticated
|
||||||
|
*
|
||||||
|
* @param user - User object from the store
|
||||||
|
* @param profile - Optional user profile object (for onboarding status, role, use_case)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const identifyUser = (user: IUser, profile?: TUserProfile) => {
|
||||||
|
if (!posthog || !user) return;
|
||||||
|
|
||||||
|
posthog.identify(user.id, {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
first_name: user.first_name,
|
||||||
|
last_name: user.last_name,
|
||||||
|
display_name: user.display_name,
|
||||||
|
date_joined: user.date_joined,
|
||||||
|
last_login_medium: user.last_login_medium || "EMAIL",
|
||||||
|
timezone: user.user_timezone,
|
||||||
|
is_email_verified: user.is_email_verified,
|
||||||
|
is_onboarded: profile?.is_onboarded || false,
|
||||||
|
role: profile?.role || null,
|
||||||
|
use_case: profile?.use_case || null,
|
||||||
|
last_workspace_id: user.last_workspace_id || null,
|
||||||
|
language: profile?.language || null,
|
||||||
|
last_login_time: user.last_login_time || null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* WORKSPACE GROUP TRACKING
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join workspace group properties in PostHog
|
||||||
|
* Call this whenever a user views a workspace (e.g., on workspace switch)
|
||||||
|
*
|
||||||
|
* @param workspace - Workspace object
|
||||||
|
*/
|
||||||
|
export const joinWorkspaceGroup = (workspace: Partial<IWorkspace>) => {
|
||||||
|
if (!posthog || !workspace.slug) return;
|
||||||
|
|
||||||
|
posthog.group("workspace", workspace.slug, {
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_name: workspace.name,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
workspace_size: workspace.organization_size,
|
||||||
|
created_at: workspace.created_at instanceof Date ? workspace.created_at.toISOString() : workspace.created_at,
|
||||||
|
owner_user_id: workspace.owner?.id || workspace.created_by,
|
||||||
|
is_deleted: false,
|
||||||
|
deleted_at: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* GENERIC EVENT TRACKING
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic event tracking function with workspace context
|
||||||
|
* All workspace events must include workspace_id, role, and groups
|
||||||
|
*
|
||||||
|
* @param eventName - Event name in snake_case (e.g., "workspace_created")
|
||||||
|
* @param properties - Event-specific properties
|
||||||
|
* @param workspaceSlug - Workspace slug for group association
|
||||||
|
* @param role - User's role in the workspace
|
||||||
|
*/
|
||||||
|
export const trackEvent = (eventName: string, properties: Record<string, unknown>, role: TUserRole) => {
|
||||||
|
if (!posthog) return;
|
||||||
|
|
||||||
|
const eventProperties = {
|
||||||
|
...properties,
|
||||||
|
role: role || "unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
posthog.capture(eventName, eventProperties);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* LIFECYCLE EVENTS
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track workspace creation
|
||||||
|
* Call this immediately after a workspace is created
|
||||||
|
*/
|
||||||
|
export const trackWorkspaceCreated = (workspace: IWorkspace, user: IUser, role: TUserRole) => {
|
||||||
|
joinWorkspaceGroup(workspace);
|
||||||
|
trackEvent(
|
||||||
|
"workspace_created",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
workspace_name: workspace.name,
|
||||||
|
created_at: workspace.created_at instanceof Date ? workspace.created_at.toISOString() : workspace.created_at,
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track workspace deletion
|
||||||
|
*/
|
||||||
|
export const trackWorkspaceDeleted = (workspace: IWorkspace, user: IUser, role: TUserRole) => {
|
||||||
|
trackEvent(
|
||||||
|
"workspace_deleted",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
deleted_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* PRODUCT ACTIVATION EVENTS
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track project creation
|
||||||
|
*/
|
||||||
|
export const trackProjectCreated = (
|
||||||
|
project: { id: string; created_at: string | Date },
|
||||||
|
workspace: IWorkspace,
|
||||||
|
user: IUser,
|
||||||
|
role: TUserRole
|
||||||
|
) => {
|
||||||
|
trackEvent(
|
||||||
|
"project_created",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
project_id: project.id,
|
||||||
|
created_at: project.created_at instanceof Date ? project.created_at.toISOString() : project.created_at,
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track work item creation
|
||||||
|
*/
|
||||||
|
export const trackWorkItemCreated = (
|
||||||
|
workItem: { id: string; type?: string; created_at: string | Date },
|
||||||
|
project: { id: string },
|
||||||
|
workspace: IWorkspace,
|
||||||
|
user: IUser,
|
||||||
|
role: TUserRole
|
||||||
|
) => {
|
||||||
|
trackEvent(
|
||||||
|
"work_item_created",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
project_id: project.id,
|
||||||
|
work_item_id: workItem.id,
|
||||||
|
work_item_type: workItem.type,
|
||||||
|
created_at: workItem.created_at instanceof Date ? workItem.created_at.toISOString() : workItem.created_at,
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track cycle creation
|
||||||
|
*/
|
||||||
|
export const trackCycleCreated = (
|
||||||
|
cycle: { id: string; length_days?: number; created_at: string | Date },
|
||||||
|
project: { id: string },
|
||||||
|
workspace: IWorkspace,
|
||||||
|
user: IUser,
|
||||||
|
role: TUserRole
|
||||||
|
) => {
|
||||||
|
trackEvent(
|
||||||
|
"cycle_created",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
project_id: project.id,
|
||||||
|
cycle_id: cycle.id,
|
||||||
|
cycle_length_days: cycle.length_days || null,
|
||||||
|
created_at: cycle.created_at instanceof Date ? cycle.created_at.toISOString() : cycle.created_at,
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track page creation
|
||||||
|
*/
|
||||||
|
export const trackPageCreated = (
|
||||||
|
page: { id: string; created_at: string | Date; project_id?: string | null },
|
||||||
|
workspace: IWorkspace,
|
||||||
|
user: IUser,
|
||||||
|
location: "project" | "wiki" | "teamspace" | "workitem",
|
||||||
|
role: TUserRole
|
||||||
|
) => {
|
||||||
|
trackEvent(
|
||||||
|
"page_created",
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
workspace_slug: workspace.slug,
|
||||||
|
page_id: page.id,
|
||||||
|
location,
|
||||||
|
project_id: page.project_id || null,
|
||||||
|
created_at: page.created_at instanceof Date ? page.created_at.toISOString() : page.created_at,
|
||||||
|
},
|
||||||
|
role
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,6 +18,9 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
|||||||
import { CycleService } from "@/services/cycle.service";
|
import { CycleService } from "@/services/cycle.service";
|
||||||
// local imports
|
// local imports
|
||||||
import { CycleForm } from "./form";
|
import { CycleForm } from "./form";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
import { getUserRoleString, trackCycleCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
type CycleModalProps = {
|
type CycleModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -39,6 +42,10 @@ export function CycleCreateUpdateModal(props: CycleModalProps) {
|
|||||||
const { createCycle, updateCycleDetails } = useCycle();
|
const { createCycle, updateCycleDetails } = useCycle();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const { setValue: setCycleTab } = useLocalStorage<TCycleTabOptions>("cycle_tab", "active");
|
const { setValue: setCycleTab } = useLocalStorage<TCycleTabOptions>("cycle_tab", "active");
|
||||||
|
|
||||||
const handleCreateCycle = async (payload: Partial<ICycle>) => {
|
const handleCreateCycle = async (payload: Partial<ICycle>) => {
|
||||||
@@ -62,12 +69,16 @@ export function CycleCreateUpdateModal(props: CycleModalProps) {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Cycle created successfully.",
|
message: "Cycle created successfully.",
|
||||||
});
|
});
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser) {
|
||||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackCycleCreated(
|
||||||
id: res.id,
|
{ id: res.id, created_at: new Date().toISOString() },
|
||||||
},
|
{ id: projectId },
|
||||||
});
|
currentWorkspace,
|
||||||
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setToast({
|
setToast({
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import { FileService } from "@/services/file.service";
|
|||||||
import { InboxIssueDescription } from "./issue-description";
|
import { InboxIssueDescription } from "./issue-description";
|
||||||
import { InboxIssueProperties } from "./issue-properties";
|
import { InboxIssueProperties } from "./issue-properties";
|
||||||
import { InboxIssueTitle } from "./issue-title";
|
import { InboxIssueTitle } from "./issue-title";
|
||||||
|
import { getUserRoleString, trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
|
||||||
const fileService = new FileService();
|
const fileService = new FileService();
|
||||||
|
|
||||||
@@ -69,6 +71,9 @@ export const InboxIssueCreateRoot = observer(function InboxIssueCreateRoot(props
|
|||||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// states
|
// states
|
||||||
const [createMore, setCreateMore] = useState<boolean>(false);
|
const [createMore, setCreateMore] = useState<boolean>(false);
|
||||||
@@ -170,12 +175,16 @@ export const InboxIssueCreateRoot = observer(function InboxIssueCreateRoot(props
|
|||||||
descriptionEditorRef?.current?.clearEditor();
|
descriptionEditorRef?.current?.clearEditor();
|
||||||
setFormData(defaultIssueData);
|
setFormData(defaultIssueData);
|
||||||
}
|
}
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser) {
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackWorkItemCreated(
|
||||||
id: res?.issue?.id,
|
{ id: res?.issue?.id ?? "", created_at: new Date().toISOString() },
|
||||||
},
|
{ id: projectId },
|
||||||
});
|
currentWorkspace,
|
||||||
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: `Success!`,
|
title: `Success!`,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
|||||||
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues/quick-add";
|
import { QuickAddIssueFormRoot } from "@/plane-web/components/issues/quick-add";
|
||||||
// local imports
|
// local imports
|
||||||
import { CreateIssueToastActionItems } from "../../create-issue-toast-action-items";
|
import { CreateIssueToastActionItems } from "../../create-issue-toast-action-items";
|
||||||
|
import { getUserRoleString, trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
|
||||||
export type TQuickAddIssueForm = {
|
export type TQuickAddIssueForm = {
|
||||||
ref: React.RefObject<HTMLFormElement>;
|
ref: React.RefObject<HTMLFormElement>;
|
||||||
@@ -67,6 +70,12 @@ export const QuickAddIssueRoot = observer(function QuickAddIssueRoot(props: TQui
|
|||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
// states
|
// states
|
||||||
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
|
const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false);
|
||||||
|
|
||||||
|
// store hooks
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
reset,
|
reset,
|
||||||
@@ -127,20 +136,26 @@ export const QuickAddIssueRoot = observer(function QuickAddIssueRoot(props: TQui
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await quickAddPromise
|
const quickAddRes = await quickAddPromise;
|
||||||
.then((res) => {
|
|
||||||
captureSuccess({
|
try {
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
if (currentWorkspace && currentUser && quickAddRes) {
|
||||||
payload: { id: res?.id },
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
});
|
trackWorkItemCreated(
|
||||||
})
|
{ id: quickAddRes.id, created_at: new Date().toISOString() },
|
||||||
.catch((error) => {
|
{ id: projectId.toString() },
|
||||||
|
currentWorkspace,
|
||||||
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
captureError({
|
captureError({
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
||||||
payload: { id: payload.id },
|
payload: { id: payload.id },
|
||||||
error: error as Error,
|
error: error as Error,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import { DraftIssueLayout } from "./draft-issue-layout";
|
|||||||
import { IssueFormRoot } from "./form";
|
import { IssueFormRoot } from "./form";
|
||||||
import type { IssueFormProps } from "./form";
|
import type { IssueFormProps } from "./form";
|
||||||
import type { IssuesModalProps } from "./modal";
|
import type { IssuesModalProps } from "./modal";
|
||||||
|
import { getUserRoleString, trackWorkItemCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
|
||||||
export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueModalBase(props: IssuesModalProps) {
|
export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueModalBase(props: IssuesModalProps) {
|
||||||
const {
|
const {
|
||||||
@@ -72,6 +75,9 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
|
|||||||
const { fetchIssue } = useIssueDetail();
|
const { fetchIssue } = useIssueDetail();
|
||||||
const { allowedProjectIds, handleCreateUpdatePropertyValues, handleCreateSubWorkItem } = useIssueModal();
|
const { allowedProjectIds, handleCreateUpdatePropertyValues, handleCreateSubWorkItem } = useIssueModal();
|
||||||
const { getProjectByIdentifier } = useProject();
|
const { getProjectByIdentifier } = useProject();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
// current store details
|
// current store details
|
||||||
const { createIssue, updateIssue } = useIssuesActions(storeType);
|
const { createIssue, updateIssue } = useIssuesActions(storeType);
|
||||||
// derived values
|
// derived values
|
||||||
@@ -240,10 +246,16 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser && response) {
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: { id: response.id },
|
trackWorkItemCreated(
|
||||||
});
|
{ id: response.id, created_at: new Date().toISOString() },
|
||||||
|
{ id: payload.project_id },
|
||||||
|
currentWorkspace,
|
||||||
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!createMore) handleClose();
|
if (!createMore) handleClose();
|
||||||
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
|
if (createMore && issueTitleRef) issueTitleRef?.current?.focus();
|
||||||
setDescription("<p></p>");
|
setDescription("<p></p>");
|
||||||
@@ -319,10 +331,17 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
|
|||||||
title: t("success"),
|
title: t("success"),
|
||||||
message: t("issue_updated_successfully"),
|
message: t("issue_updated_successfully"),
|
||||||
});
|
});
|
||||||
captureSuccess({
|
|
||||||
eventName: WORK_ITEM_TRACKER_EVENTS.update,
|
if (currentWorkspace && currentUser) {
|
||||||
payload: { id: data.id },
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
});
|
trackWorkItemCreated(
|
||||||
|
{ id: data.id, created_at: new Date().toISOString() },
|
||||||
|
{ id: payload.project_id },
|
||||||
|
currentWorkspace,
|
||||||
|
currentUser,
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import type { EPageStoreType } from "@/plane-web/hooks/store";
|
|||||||
import { usePageStore } from "@/plane-web/hooks/store";
|
import { usePageStore } from "@/plane-web/hooks/store";
|
||||||
// local imports
|
// local imports
|
||||||
import { PageForm } from "./page-form";
|
import { PageForm } from "./page-form";
|
||||||
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
import { getUserRoleString, trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@@ -45,6 +48,10 @@ export function CreatePageModal(props: Props) {
|
|||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { createPage } = usePageStore(storeType);
|
const { createPage } = usePageStore(storeType);
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
||||||
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
||||||
|
|
||||||
@@ -64,12 +71,16 @@ export function CreatePageModal(props: Props) {
|
|||||||
try {
|
try {
|
||||||
const pageData = await createPage(pageFormData);
|
const pageData = await createPage(pageFormData);
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser) {
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackPageCreated(
|
||||||
id: pageData.id,
|
{ id: pageData.id ?? "", created_at: new Date().toISOString() },
|
||||||
},
|
currentWorkspace,
|
||||||
});
|
currentUser,
|
||||||
|
"project",
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
handleStateClear();
|
handleStateClear();
|
||||||
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ import { EUserProjectRoles } from "@plane/types";
|
|||||||
import { PageLoader } from "@/components/pages/loaders/page-loader";
|
import { PageLoader } from "@/components/pages/loaders/page-loader";
|
||||||
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useProject } from "@/hooks/store/use-project";
|
import { useProject } from "@/hooks/store/use-project";
|
||||||
import { useUserPermissions } from "@/hooks/store/user";
|
import { useUser, useUserPermissions } from "@/hooks/store/user";
|
||||||
// plane web hooks
|
// plane web hooks
|
||||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||||
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
|
import { getUserRoleString, trackPageCreated } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -35,8 +37,10 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
|
|||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { isAnyPageAvailable, getCurrentProjectFilteredPageIdsByTab, getCurrentProjectPageIdsByTab, loader } =
|
const { isAnyPageAvailable, getCurrentProjectFilteredPageIdsByTab, getCurrentProjectPageIdsByTab, loader } =
|
||||||
usePageStore(storeType);
|
usePageStore(storeType);
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
const { createPage } = usePageStore(EPageStoreType.PROJECT);
|
const { createPage } = usePageStore(EPageStoreType.PROJECT);
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
// states
|
// states
|
||||||
const [isCreatingPage, setIsCreatingPage] = useState(false);
|
const [isCreatingPage, setIsCreatingPage] = useState(false);
|
||||||
// router
|
// router
|
||||||
@@ -60,13 +64,16 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
|
|||||||
|
|
||||||
await createPage(payload)
|
await createPage(payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
captureSuccess({
|
if (currentWorkspace && currentUser && res?.id) {
|
||||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
const role = getWorkspaceRoleByWorkspaceSlug(currentWorkspace.slug);
|
||||||
payload: {
|
trackPageCreated(
|
||||||
id: res?.id,
|
{ id: res.id, created_at: new Date().toISOString() },
|
||||||
state: "SUCCESS",
|
currentWorkspace,
|
||||||
},
|
currentUser,
|
||||||
});
|
"project",
|
||||||
|
getUserRoleString(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
|
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
|
||||||
router.push(pageId);
|
router.push(pageId);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ import { Input } from "@plane/ui";
|
|||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
import { useUserSettings } from "@/hooks/store/user";
|
import { useUser, useUserPermissions, useUserSettings } from "@/hooks/store/user";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
|
import { getUserRoleString, trackWorkspaceDeleted } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: IWorkspace | null;
|
data: IWorkspace | null;
|
||||||
@@ -36,6 +37,9 @@ export const DeleteWorkspaceForm = observer(function DeleteWorkspaceForm(props:
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
||||||
const { fetchCurrentUserSettings } = useUserSettings();
|
const { fetchCurrentUserSettings } = useUserSettings();
|
||||||
|
const { getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||||
|
const { data: currentUser } = useUser();
|
||||||
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -64,10 +68,12 @@ export const DeleteWorkspaceForm = observer(function DeleteWorkspaceForm(props:
|
|||||||
await fetchCurrentUserSettings();
|
await fetchCurrentUserSettings();
|
||||||
handleClose();
|
handleClose();
|
||||||
router.push(getWorkspaceRedirectionUrl());
|
router.push(getWorkspaceRedirectionUrl());
|
||||||
captureSuccess({
|
|
||||||
eventName: WORKSPACE_TRACKER_EVENTS.delete,
|
if (currentUser && data) {
|
||||||
payload: { slug: data.slug },
|
const role = getWorkspaceRoleByWorkspaceSlug(data.slug);
|
||||||
});
|
trackWorkspaceDeleted(data, currentUser, getUserRoleString(role));
|
||||||
|
}
|
||||||
|
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: t("workspace_settings.settings.general.delete_modal.success_title"),
|
title: t("workspace_settings.settings.general.delete_modal.success_title"),
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import type { ReactNode } from "react";
|
|
||||||
import { lazy, Suspense, useEffect, useCallback, useRef, useState } from "react";
|
|
||||||
import { PostHogProvider as PHProvider } from "@posthog/react";
|
import { PostHogProvider as PHProvider } from "@posthog/react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
|
||||||
// constants
|
// constants
|
||||||
import { GROUP_WORKSPACE_TRACKER_EVENT } from "@plane/constants";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { captureClick, joinEventGroup } from "@/helpers/event-tracker.helper";
|
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||||
|
import { identifyUser, joinWorkspaceGroup } from "@/plane-web/helpers/event-tracker-v2.helper";
|
||||||
import { useInstance } from "@/hooks/store/use-instance";
|
import { useInstance } from "@/hooks/store/use-instance";
|
||||||
|
import { useUser, useUserProfile } from "@/hooks/store/user";
|
||||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||||
import { useUser, useUserPermissions, useUserProfile } from "@/hooks/store/user";
|
|
||||||
// dynamic imports
|
// dynamic imports
|
||||||
const PostHogPageView = lazy(function PostHogPageView() {
|
const PostHogPageView = lazy(function PostHogPageView() {
|
||||||
return import("@/lib/posthog-view");
|
return import("@/lib/posthog-view");
|
||||||
@@ -24,44 +23,27 @@ const PostHogProvider = observer(function PostHogProvider(props: IPosthogWrapper
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
const { data: profile } = useUserProfile();
|
const { data: profile } = useUserProfile();
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstance();
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { getWorkspaceRoleByWorkspaceSlug, getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
|
|
||||||
// refs
|
// refs
|
||||||
const isInitializedRef = useRef(false);
|
const isInitializedRef = useRef(false);
|
||||||
// states
|
// states
|
||||||
const [hydrated, setHydrated] = useState(false);
|
const [hydrated, setHydrated] = useState(false);
|
||||||
// derived values
|
|
||||||
const currentProjectRole = getProjectRoleByWorkspaceSlugAndProjectId(
|
|
||||||
workspaceSlug?.toString(),
|
|
||||||
projectId?.toString()
|
|
||||||
);
|
|
||||||
const currentWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug?.toString());
|
|
||||||
const is_telemetry_enabled = instance?.is_telemetry_enabled || false;
|
const is_telemetry_enabled = instance?.is_telemetry_enabled || false;
|
||||||
const is_posthog_enabled = process.env.VITE_POSTHOG_KEY && process.env.VITE_POSTHOG_HOST && is_telemetry_enabled;
|
const is_posthog_enabled = process.env.VITE_POSTHOG_KEY && process.env.VITE_POSTHOG_HOST && is_telemetry_enabled;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && profile && hydrated) {
|
if (user && profile && hydrated && is_posthog_enabled) {
|
||||||
// Identify sends an event, so you want may want to limit how often you call it
|
identifyUser(user, profile);
|
||||||
posthog?.identify(user.email, {
|
|
||||||
id: user.id,
|
|
||||||
first_name: user.first_name,
|
|
||||||
last_name: user.last_name,
|
|
||||||
email: user.email,
|
|
||||||
display_name: user.display_name,
|
|
||||||
date_joined: user.date_joined,
|
|
||||||
last_login_medium: user.last_login_medium,
|
|
||||||
is_email_verified: user.is_email_verified,
|
|
||||||
timezone: user.user_timezone,
|
|
||||||
is_onboarded: profile.is_onboarded,
|
|
||||||
role: profile.role,
|
|
||||||
use_case: profile.use_case,
|
|
||||||
language: profile.language,
|
|
||||||
last_workspace_id: profile.last_workspace_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [user, profile, hydrated]);
|
}, [user, profile, hydrated, is_posthog_enabled]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentWorkspace && hydrated && is_posthog_enabled) {
|
||||||
|
joinWorkspaceGroup(currentWorkspace);
|
||||||
|
}
|
||||||
|
}, [currentWorkspace, hydrated, is_posthog_enabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitializedRef.current) return; // prevent multiple initializations
|
if (isInitializedRef.current) return; // prevent multiple initializations
|
||||||
|
|||||||
3
apps/web/ee/helpers/event-tracker-v2.helper.ts
Normal file
3
apps/web/ee/helpers/event-tracker-v2.helper.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "ce/helpers/event-tracker-v2.helper";
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ export interface IUser extends IUserLite {
|
|||||||
user_timezone: string;
|
user_timezone: string;
|
||||||
username: string;
|
username: string;
|
||||||
last_login_medium: TLoginMediums;
|
last_login_medium: TLoginMediums;
|
||||||
|
last_login_time: string | null;
|
||||||
theme: IUserTheme;
|
theme: IUserTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user