Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60fe33d3f6 | ||
|
|
0a50a61644 | ||
|
|
7c633f67c6 | ||
|
|
b17ee7282f | ||
|
|
e9f58a9b26 | ||
|
|
d4d49f5abc | ||
|
|
72d9318994 | ||
|
|
37fd368833 | ||
|
|
47f3dd4f35 | ||
|
|
477d5f74d9 | ||
|
|
59140aab74 | ||
|
|
9cd75e62c2 | ||
|
|
785f85754e | ||
|
|
90991dfeb3 | ||
|
|
be77cf89c8 | ||
|
|
db1caa79eb | ||
|
|
3545008a1e | ||
|
|
f5cc076ca6 | ||
|
|
bc390536c6 | ||
|
|
7ec3673ce3 | ||
|
|
06dc8020c4 | ||
|
|
0d58c18fdd | ||
|
|
9f8872e431 | ||
|
|
4b510b8dfd | ||
|
|
ef93781ce6 | ||
|
|
5c30d0d911 | ||
|
|
ff0112fc91 | ||
|
|
8011b76211 | ||
|
|
5301f63ddb | ||
|
|
f366f9531c | ||
|
|
f19befd109 | ||
|
|
bb56fc36bf | ||
|
|
edc3c817dc | ||
|
|
f65c1f4510 |
@@ -12,59 +12,6 @@ export type EventProps = {
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export const getWorkspaceEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
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) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.id,
|
||||
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,
|
||||
updated_at: payload.updated_at,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getCycleEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.project,
|
||||
cycle_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
start_date: payload.start_date,
|
||||
target_date: payload.target_date,
|
||||
cycle_status: payload.status,
|
||||
changed_properties: payload.changed_properties,
|
||||
state: payload.state,
|
||||
element: payload.element,
|
||||
});
|
||||
|
||||
export const getModuleEventPayload = (payload: any) => ({
|
||||
workspace_id: payload.workspace_id,
|
||||
project_id: payload.project,
|
||||
module_id: payload.id,
|
||||
created_at: payload.created_at,
|
||||
updated_at: payload.updated_at,
|
||||
start_date: payload.start_date,
|
||||
target_date: payload.target_date,
|
||||
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,
|
||||
@@ -159,13 +106,37 @@ export const PROJECT_TRACKER_EVENTS = {
|
||||
delete: "project_deleted",
|
||||
};
|
||||
|
||||
export const PROJECT_TRACKER_ELEMENTS = {
|
||||
EXTENDED_SIDEBAR_ADD_BUTTON: "extended_sidebar_add_project_button",
|
||||
SIDEBAR_CREATE_PROJECT_BUTTON: "sidebar_create_project_button",
|
||||
SIDEBAR_CREATE_PROJECT_TOOLTIP: "sidebar_create_project_tooltip",
|
||||
COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_project_button",
|
||||
COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_project_button",
|
||||
EMPTY_STATE_CREATE_PROJECT_BUTTON: "empty_state_create_project_button",
|
||||
CREATE_HEADER_BUTTON: "create_project_header_button",
|
||||
CREATE_FIRST_PROJECT_BUTTON: "create_first_project_button",
|
||||
DELETE_PROJECT_BUTTON: "delete_project_button",
|
||||
UPDATE_PROJECT_BUTTON: "update_project_button",
|
||||
};
|
||||
|
||||
export const CYCLE_TRACKER_EVENTS = {
|
||||
create: "cycle_created",
|
||||
update: "cycle_updated",
|
||||
delete: "cycle_deleted",
|
||||
favorite: "cycle_favorited",
|
||||
unfavorite: "cycle_unfavorited",
|
||||
archive: "cycle_archived",
|
||||
restore: "cycle_restored",
|
||||
};
|
||||
export const CYCLE_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "right_header_add_cycle_button",
|
||||
EMPTY_STATE_ADD_BUTTON: "empty_state_add_cycle_button",
|
||||
COMMAND_PALETTE_ADD_ITEM: "command_palette_add_cycle_item",
|
||||
RIGHT_SIDEBAR: "cycle_right_sidebar",
|
||||
QUICK_ACTIONS: "cycle_quick_actions",
|
||||
CONTEXT_MENU: "cycle_context_menu",
|
||||
LIST_ITEM: "cycle_list_item",
|
||||
} as const;
|
||||
|
||||
export const MODULE_TRACKER_EVENTS = {
|
||||
create: "module_created",
|
||||
@@ -173,12 +144,24 @@ export const MODULE_TRACKER_EVENTS = {
|
||||
delete: "module_deleted",
|
||||
favorite: "module_favorited",
|
||||
unfavorite: "module_unfavorited",
|
||||
archive: "module_archived",
|
||||
restore: "module_restored",
|
||||
link: {
|
||||
create: "module_link_created",
|
||||
update: "module_link_updated",
|
||||
delete: "module_link_deleted",
|
||||
},
|
||||
};
|
||||
export const MODULE_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "right_header_add_module_button",
|
||||
EMPTY_STATE_ADD_BUTTON: "empty_state_add_module_button",
|
||||
COMMAND_PALETTE_ADD_ITEM: "command_palette_add_module_item",
|
||||
RIGHT_SIDEBAR: "module_right_sidebar",
|
||||
QUICK_ACTIONS: "module_quick_actions",
|
||||
CONTEXT_MENU: "module_context_menu",
|
||||
LIST_ITEM: "module_list_item",
|
||||
CARD_ITEM: "module_card_item",
|
||||
} as const;
|
||||
|
||||
export const WORK_ITEM_TRACKER_EVENTS = {
|
||||
create: "work_item_created",
|
||||
@@ -193,13 +176,41 @@ export const STATE_TRACKER_EVENTS = {
|
||||
update: "state_updated",
|
||||
delete: "state_deleted",
|
||||
};
|
||||
export const STATE_TRACKER_ELEMENTS = {
|
||||
STATE_GROUP_ADD_BUTTON: "state_group_add_button",
|
||||
STATE_LIST_DELETE_BUTTON: "state_list_delete_button",
|
||||
STATE_LIST_EDIT_BUTTON: "state_list_edit_button",
|
||||
};
|
||||
|
||||
export const PROJECT_PAGE_TRACKER_EVENTS = {
|
||||
create: "project_page_created",
|
||||
update: "project_page_updated",
|
||||
delete: "project_page_deleted",
|
||||
archive: "project_page_archived",
|
||||
restore: "project_page_restored",
|
||||
lock: "project_page_locked",
|
||||
unlock: "project_page_unlocked",
|
||||
access_update: "project_page_access_updated",
|
||||
duplicate: "project_page_duplicated",
|
||||
favorite: "project_page_favorited",
|
||||
unfavorite: "project_page_unfavorited",
|
||||
move: "project_page_moved",
|
||||
};
|
||||
|
||||
export const PROJECT_PAGE_TRACKER_ELEMENTS = {
|
||||
COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON: "command_palette_shortcut_create_page_button",
|
||||
EMPTY_STATE_CREATE_BUTTON: "empty_state_create_page_button",
|
||||
COMMAND_PALETTE_CREATE_BUTTON: "command_palette_create_page_button",
|
||||
CONTEXT_MENU: "page_context_menu",
|
||||
QUICK_ACTIONS: "page_quick_actions",
|
||||
LIST_ITEM: "page_list_item",
|
||||
FAVORITE_BUTTON: "page_favorite_button",
|
||||
ARCHIVE_BUTTON: "page_archive_button",
|
||||
LOCK_BUTTON: "page_lock_button",
|
||||
ACCESS_TOGGLE: "page_access_toggle",
|
||||
DUPLICATE_BUTTON: "page_duplicate_button",
|
||||
} as const;
|
||||
|
||||
export const MEMBER_TRACKER_EVENTS = {
|
||||
invite: "member_invited",
|
||||
accept: "member_accepted",
|
||||
@@ -211,48 +222,77 @@ export const MEMBER_TRACKER_EVENTS = {
|
||||
leave: "workspace_member_left",
|
||||
},
|
||||
};
|
||||
export const MEMBER_TRACKER_ELEMENTS = {
|
||||
HEADER_ADD_BUTTON: "header_add_member_button",
|
||||
ACCEPT_INVITATION_BUTTON: "accept_invitation_button",
|
||||
ONBOARDING_JOIN_WORKSPACE: "workspace_join_continue_to_workspace_button",
|
||||
ONBOARDING_INVITE_MEMBER: "invite_member_continue_button",
|
||||
SIDEBAR_PROJECT_QUICK_ACTIONS: "sidebar_project_quick_actions",
|
||||
PROJECT_MEMBER_TABLE_CONTEXT_MENU: "project_member_table_context_menu",
|
||||
WORKSPACE_MEMBER_TABLE_CONTEXT_MENU: "workspace_member_table_context_menu",
|
||||
WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU: "workspace_invitations_list_context_menu",
|
||||
} as const;
|
||||
|
||||
export const AUTH_TRACKER_EVENTS = {
|
||||
navigate: {
|
||||
sign_up: "navigate_to_sign_up_page",
|
||||
sign_in: "navigate_to_sign_in_page",
|
||||
},
|
||||
code_verify: "code_verified",
|
||||
sign_up_with_password: "sign_up_with_password",
|
||||
sign_in_with_password: "sign_in_with_password",
|
||||
sign_in_with_code: "sign_in_with_magic_link",
|
||||
forgot_password: "forgot_password_clicked",
|
||||
new_code_requested: "new_code_requested",
|
||||
};
|
||||
export const AUTH_TRACKER_ELEMENTS = {
|
||||
NAVIGATE_TO_SIGN_UP: "navigate_to_sign_up",
|
||||
FORGOT_PASSWORD_FROM_SIGNIN: "forgot_password_from_signin",
|
||||
SIGNUP_FROM_FORGOT_PASSWORD: "signup_from_forgot_password",
|
||||
SIGN_IN_FROM_SIGNUP: "sign_in_from_signup",
|
||||
SIGN_IN_WITH_UNIQUE_CODE: "sign_in_with_unique_code",
|
||||
REQUEST_NEW_CODE: "request_new_code",
|
||||
VERIFY_CODE: "verify_code",
|
||||
};
|
||||
|
||||
export const PRODUCT_TOUR_TRACKER_EVENTS = {
|
||||
start: "product_tour_started",
|
||||
complete: "product_tour_completed",
|
||||
skip: "product_tour_skipped",
|
||||
};
|
||||
|
||||
export const GLOBAL_VIEW_TOUR_TRACKER_EVENTS = {
|
||||
export const GLOBAL_VIEW_TRACKER_EVENTS = {
|
||||
create: "global_view_created",
|
||||
update: "global_view_updated",
|
||||
delete: "global_view_deleted",
|
||||
open: "global_view_opened",
|
||||
};
|
||||
export const GLOBAL_VIEW_TRACKER_ELEMENTS = {
|
||||
RIGHT_HEADER_ADD_BUTTON: "global_view_right_header_add_button",
|
||||
HEADER_SAVE_VIEW_BUTTON: "global_view_header_save_view_button",
|
||||
QUICK_ACTIONS: "global_view_quick_actions",
|
||||
LIST_ITEM: "global_view_list_item",
|
||||
};
|
||||
|
||||
export const PRODUCT_TOUR_TRACKER_EVENTS = {
|
||||
complete: "product_tour_completed",
|
||||
};
|
||||
export const PRODUCT_TOUR_TRACKER_ELEMENTS = {
|
||||
START_BUTTON: "product_tour_start_button",
|
||||
SKIP_BUTTON: "product_tour_skip_button",
|
||||
CREATE_PROJECT_BUTTON: "product_tour_create_project_button",
|
||||
};
|
||||
|
||||
export const NOTIFICATION_TRACKER_EVENTS = {
|
||||
archive: "notification_archived",
|
||||
unarchive: "notification_unarchived",
|
||||
mark_read: "notification_marked_read",
|
||||
mark_unread: "notification_marked_unread",
|
||||
all_marked_read: "all_notifications_marked_read",
|
||||
};
|
||||
export const NOTIFICATION_TRACKER_ELEMENTS = {
|
||||
MARK_ALL_AS_READ_BUTTON: "mark_all_as_read_button",
|
||||
ARCHIVE_UNARCHIVE_BUTTON: "archive_unarchive_button",
|
||||
MARK_READ_UNREAD_BUTTON: "mark_read_unread_button",
|
||||
};
|
||||
|
||||
export const USER_TRACKER_EVENTS = {
|
||||
add_details: "user_details_added",
|
||||
onboarding_complete: "user_onboarding_completed",
|
||||
};
|
||||
|
||||
export const ONBOARDING_TRACKER_EVENTS = {
|
||||
root: "onboarding",
|
||||
step_1: "onboarding_step_1",
|
||||
step_2: "onboarding_step_2",
|
||||
export const ONBOARDING_TRACKER_ELEMENTS = {
|
||||
PROFILE_SETUP_FORM: "onboarding_profile_setup_form",
|
||||
};
|
||||
|
||||
export const SIDEBAR_TRACKER_EVENTS = {
|
||||
click: "sidenav_clicked",
|
||||
export const SIDEBAR_TRACKER_ELEMENTS = {
|
||||
USER_MENU_ITEM: "sidenav_user_menu_item",
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
// plane package imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { type TabItem, Tabs } from "@plane/ui";
|
||||
// components
|
||||
@@ -12,6 +12,7 @@ import AnalyticsFilterActions from "@/components/analytics/analytics-filter-acti
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
|
||||
@@ -103,6 +104,7 @@ const AnalyticsPage = observer((props: Props) => {
|
||||
onClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { Plus, Search } from "lucide-react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
import { cn, copyUrlToClipboard, orderJoinedProjects } from "@plane/utils";
|
||||
@@ -122,6 +122,7 @@ export const ExtendedProjectSidebar = observer(() => {
|
||||
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EXTENDED_SIDEBAR_ADD_BUTTON}
|
||||
className="p-0.5 rounded hover:bg-custom-sidebar-background-80 flex-shrink-0"
|
||||
onClick={() => {
|
||||
setIsProjectModalOpen(true);
|
||||
|
||||
+3
-4
@@ -4,13 +4,13 @@ import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { CyclesViewHeader } from "@/components/cycles";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
||||
@@ -23,7 +23,6 @@ export const CyclesListHeader: FC = observer(() => {
|
||||
|
||||
// store hooks
|
||||
const { toggleCreateCycleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { t } = useTranslation();
|
||||
@@ -51,8 +50,8 @@ export const CyclesListHeader: FC = observer(() => {
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Cycles page");
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
>
|
||||
|
||||
+3
-4
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TCycleFilters } from "@plane/types";
|
||||
// components
|
||||
@@ -16,7 +16,7 @@ import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { CycleModuleListLayout } from "@/components/ui";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
@@ -24,7 +24,6 @@ const ProjectCyclesPage = observer(() => {
|
||||
// states
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { currentProjectCycleIds, loader } = useCycle();
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// router
|
||||
@@ -100,8 +99,8 @@ const ProjectCyclesPage = observer(() => {
|
||||
label={t("project_cycles.empty_state.general.primary_button.text")}
|
||||
title={t("project_cycles.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_cycles.empty_state.general.primary_button.comic.description")}
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Cycle empty state");
|
||||
setCreateModal(true);
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
|
||||
+3
-4
@@ -3,14 +3,14 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { ModuleViewHeader } from "@/components/modules";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs";
|
||||
@@ -22,7 +22,6 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
const { toggleCreateModuleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { loader } = useProject();
|
||||
@@ -55,8 +54,8 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Modules page");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
EIssueLayoutTypes
|
||||
EIssueLayoutTypes,
|
||||
GLOBAL_VIEW_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
@@ -228,7 +229,12 @@ export const GlobalIssuesHeader = observer(() => {
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => setCreateViewModal(true)}
|
||||
>
|
||||
{t("workspace_views.add_view")}
|
||||
</Button>
|
||||
<div className="hidden md:block">
|
||||
|
||||
@@ -5,12 +5,17 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Search } from "lucide-react";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
MEMBER_TRACKER_ELEMENTS,
|
||||
MEMBER_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { cn, getUserRole } from "@plane/utils";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { CountChip } from "@/components/common";
|
||||
@@ -19,7 +24,8 @@ import { SettingsContentWrapper } from "@/components/settings";
|
||||
import { WorkspaceMembersList } from "@/components/workspace";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { BillingActionsButton } from "@/plane-web/components/workspace/billing";
|
||||
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members";
|
||||
@@ -32,7 +38,6 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const {
|
||||
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
|
||||
} = useMember();
|
||||
@@ -52,16 +57,11 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||
.then(() => {
|
||||
setInviteModal(false);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...data.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace settings member page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -70,16 +70,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...data.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role as unknown as EUserPermissions),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "FAILED",
|
||||
element: "Workspace settings member page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -129,7 +125,12 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
/>
|
||||
</div>
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setInviteModal(true)}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
|
||||
>
|
||||
{t("workspace_settings.settings.members.add_member")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button, getButtonStyling } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useCommandPalette } from "@/hooks/store";
|
||||
@@ -27,7 +28,11 @@ const ProjectSettingsPage = () => {
|
||||
<Link href="https://plane.so/" target="_blank" className={cn(getButtonStyling("neutral-primary", "sm"))}>
|
||||
Learn more about projects
|
||||
</Link>
|
||||
<Button size="sm" onClick={() => toggleCreateProjectModal(true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => toggleCreateProjectModal(true)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||
>
|
||||
Start your first project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,14 +9,15 @@ import { Controller, useForm } from "react-hook-form";
|
||||
// icons
|
||||
import { CircleCheck } from "lucide-react";
|
||||
// plane imports
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||
import { cn, checkEmailValidity } from "@plane/utils";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
@@ -45,8 +46,6 @@ const ForgotPasswordPage = observer(() => {
|
||||
const email = searchParams.get("email");
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { config } = useInstance();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
@@ -71,8 +70,11 @@ const ForgotPasswordPage = observer(() => {
|
||||
email: formData.email,
|
||||
})
|
||||
.then(() => {
|
||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||
payload: {
|
||||
email: formData.email,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -82,8 +84,11 @@ const ForgotPasswordPage = observer(() => {
|
||||
setResendCodeTimer(30);
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(AUTH_TRACKER_EVENTS.forgot_password, {
|
||||
state: "FAILED",
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.forgot_password,
|
||||
payload: {
|
||||
email: formData.email,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -120,7 +125,7 @@ const ForgotPasswordPage = observer(() => {
|
||||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGNUP_FROM_FORGOT_PASSWORD}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.create_account")}
|
||||
|
||||
@@ -9,19 +9,20 @@ import { useTheme } from "next-themes";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { truncateText, getUserRole } from "@plane/utils";
|
||||
import { truncateText } from "@plane/utils";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common";
|
||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||
import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// services
|
||||
@@ -43,7 +44,7 @@ const UserInvitationsPage = observer(() => {
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
||||
const { joinWorkspaceMetricGroup } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
const { updateUserProfile } = useUserProfile();
|
||||
|
||||
@@ -86,14 +87,11 @@ const UserInvitationsPage = observer(() => {
|
||||
const invitation = invitations?.find((i) => i.id === firstInviteId);
|
||||
const redirectWorkspace = invitations?.find((i) => i.id === firstInviteId)?.workspace;
|
||||
joinWorkspaceMetricGroup(redirectWorkspace?.id);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
member_id: invitation?.id,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
role: getUserRole((invitation?.role as unknown as EUserPermissions)!),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "SUCCESS",
|
||||
element: "Workspace invitations page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
},
|
||||
});
|
||||
updateUserProfile({ last_workspace_id: redirectWorkspace?.id })
|
||||
.then(() => {
|
||||
@@ -111,12 +109,13 @@ const UserInvitationsPage = observer(() => {
|
||||
setIsJoiningWorkspaces(false);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "FAILED",
|
||||
element: "Workspace invitations page",
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitationsRespond?.[0],
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -194,6 +193,7 @@ const UserInvitationsPage = observer(() => {
|
||||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || invitationsRespond.length === 0}
|
||||
loading={isJoiningWorkspaces}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ACCEPT_INVITATION_BUTTON}
|
||||
>
|
||||
{t("accept_and_join")}
|
||||
</Button>
|
||||
|
||||
@@ -16,7 +16,8 @@ import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useWorkspace, useUserProfile } from "@/hooks/store";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
@@ -35,7 +36,6 @@ const OnboardingPage = observer(() => {
|
||||
const [step, setStep] = useState<EOnboardingSteps | null>(null);
|
||||
const [totalSteps, setTotalSteps] = useState<number | null>(null);
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isLoading: userLoader, data: user, updateCurrentUser } = useUser();
|
||||
const { data: profile, updateUserProfile, finishUserOnboarding } = useUserProfile();
|
||||
const { workspaces, fetchWorkspaces } = useWorkspace();
|
||||
@@ -73,10 +73,12 @@ const OnboardingPage = observer(() => {
|
||||
|
||||
await finishUserOnboarding()
|
||||
.then(() => {
|
||||
captureEvent(USER_TRACKER_EVENTS.onboarding_complete, {
|
||||
email: user.email,
|
||||
user_id: user.id,
|
||||
status: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.onboarding_complete,
|
||||
payload: {
|
||||
email: user.email,
|
||||
user_id: user.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -6,14 +6,12 @@ import Link from "next/link";
|
||||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
// constants
|
||||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
// assets
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg";
|
||||
@@ -26,8 +24,6 @@ export type AuthType = "sign-in" | "sign-up";
|
||||
const SignInPage = observer(() => {
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
@@ -54,7 +50,7 @@ const SignInPage = observer(() => {
|
||||
{t("auth.common.already_have_an_account")}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_in, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_FROM_SIGNUP}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.login")}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Link from "next/link";
|
||||
// ui
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
import { AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AuthRoot } from "@/components/account";
|
||||
import { PageHead } from "@/components/core";
|
||||
@@ -14,7 +14,7 @@ import { PageHead } from "@/components/core";
|
||||
// helpers
|
||||
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useInstance } from "@/hooks/store";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// layouts
|
||||
import DefaultLayout from "@/layouts/default-layout";
|
||||
// wrappers
|
||||
@@ -29,8 +29,6 @@ const HomePage = observer(() => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// store
|
||||
const { config } = useInstance();
|
||||
// derived values
|
||||
@@ -63,7 +61,7 @@ const HomePage = observer(() => {
|
||||
{t("auth.common.new_to_plane")}
|
||||
<Link
|
||||
href="/sign-up"
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.navigate.sign_up, {})}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.NAVIGATE_TO_SIGN_UP}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
{t("auth.common.create_account")}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { useState, useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
|
||||
// plane imports
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// store
|
||||
@@ -78,6 +81,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON}
|
||||
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
|
||||
aria-label="Lock"
|
||||
>
|
||||
@@ -90,6 +94,7 @@ export const PageLockControl = observer(({ page }: Props) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON}
|
||||
className="h-6 flex items-center gap-1 px-2 rounded text-custom-primary-100 bg-custom-primary-100/20 hover:bg-custom-primary-100/30 transition-colors"
|
||||
aria-label="Locked"
|
||||
>
|
||||
|
||||
@@ -12,7 +12,8 @@ import ProjectCommonAttributes from "@/components/project/create/common-attribut
|
||||
import ProjectCreateHeader from "@/components/project/create/header";
|
||||
import ProjectCreateButtons from "@/components/project/create/project-create-buttons";
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
@@ -32,7 +33,6 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||
const { setToFavorite, workspaceSlug, data, onClose, handleNextStep, updateCoverImageStatus } = props;
|
||||
// store
|
||||
const { t } = useTranslation();
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { addProjectToFavorites, createProject } = useProject();
|
||||
// states
|
||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||
@@ -70,25 +70,30 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||
if (coverImage) {
|
||||
await updateCoverImageStatus(res.id, coverImage);
|
||||
}
|
||||
const newPayload = {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
};
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||
payload: newPayload,
|
||||
payload: {
|
||||
identifier: formData.identifier,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("success"),
|
||||
message: t("project_created_successfully"),
|
||||
});
|
||||
|
||||
if (setToFavorite) {
|
||||
handleAddToFavorites(res.id);
|
||||
}
|
||||
handleNextStep(res.id);
|
||||
})
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
identifier: formData.identifier,
|
||||
},
|
||||
});
|
||||
if (err?.data.code === "PROJECT_NAME_ALREADY_EXIST") {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
||||
@@ -48,7 +48,11 @@ export const DeleteWorkspaceSection: FC<TDeleteWorkspace> = observer((props) =>
|
||||
{t("workspace_settings.settings.general.delete_workspace_description")}
|
||||
</span>
|
||||
<div>
|
||||
<Button variant="danger" onClick={() => setDeleteWorkspaceModal(true)}>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => setDeleteWorkspaceModal(true)}
|
||||
data-ph-element="WORKSPACE_SETTINGS_DELETE_WORKSPACE_BUTTON"
|
||||
>
|
||||
{t("workspace_settings.settings.general.delete_btn")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// types
|
||||
import {
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
PROJECT_PAGE_TRACKER_ELEMENTS,
|
||||
PROJECT_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types";
|
||||
// store
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { store } from "@/lib/store-context";
|
||||
|
||||
export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
|
||||
@@ -22,7 +29,10 @@ export const getWorkspaceShortcutsList: () => TCommandPaletteActionList = () =>
|
||||
p: {
|
||||
title: "Create a new project",
|
||||
description: "Create a new project in the current workspace",
|
||||
action: () => toggleCreateProjectModal(true),
|
||||
action: () => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON });
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -40,17 +50,26 @@ export const getProjectShortcutsList: () => TCommandPaletteActionList = () => {
|
||||
d: {
|
||||
title: "Create a new page",
|
||||
description: "Create a new page in the current project",
|
||||
action: () => toggleCreatePageModal({ isOpen: true }),
|
||||
action: () => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_SHORTCUT_CREATE_BUTTON });
|
||||
},
|
||||
},
|
||||
m: {
|
||||
title: "Create a new module",
|
||||
description: "Create a new module in the current project",
|
||||
action: () => toggleCreateModuleModal(true),
|
||||
action: () => {
|
||||
toggleCreateModuleModal(true);
|
||||
captureClick({ elementName: MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM });
|
||||
},
|
||||
},
|
||||
q: {
|
||||
title: "Create a new cycle",
|
||||
description: "Create a new cycle in the current project",
|
||||
action: () => toggleCreateCycleModal(true),
|
||||
action: () => {
|
||||
toggleCreateCycleModal(true);
|
||||
captureClick({ elementName: CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM });
|
||||
},
|
||||
},
|
||||
v: {
|
||||
title: "Create a new view",
|
||||
|
||||
@@ -6,7 +6,7 @@ import Link from "next/link";
|
||||
// icons
|
||||
import { Eye, EyeOff, Info, X, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH, AUTH_TRACKER_EVENTS, AUTH_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
import { getPasswordStrength } from "@plane/utils";
|
||||
@@ -16,7 +16,7 @@ import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/accou
|
||||
// helpers
|
||||
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
|
||||
@@ -46,8 +46,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props;
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// ref
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
// states
|
||||
@@ -77,7 +75,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
|
||||
const redirectToUniqueCodeSignIn = async () => {
|
||||
handleAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||
captureEvent(AUTH_TRACKER_EVENTS.sign_in_with_code);
|
||||
};
|
||||
|
||||
const passwordSupport =
|
||||
@@ -85,7 +82,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
<div className="w-full">
|
||||
{isSMTPConfigured ? (
|
||||
<Link
|
||||
onClick={() => captureEvent(AUTH_TRACKER_EVENTS.forgot_password)}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.FORGOT_PASSWORD_FROM_SIGNIN}
|
||||
href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`}
|
||||
className="text-xs font-medium text-custom-primary-100"
|
||||
>
|
||||
@@ -154,17 +151,32 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
: true;
|
||||
if (isPasswordValid) {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password
|
||||
);
|
||||
captureSuccess({
|
||||
eventName:
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password,
|
||||
payload: {
|
||||
email: passwordFormData.email,
|
||||
},
|
||||
});
|
||||
if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met
|
||||
} else {
|
||||
setBannerMessage(true);
|
||||
}
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
onError={() => {
|
||||
setIsSubmitting(false);
|
||||
captureError({
|
||||
eventName:
|
||||
mode === EAuthModes.SIGN_IN
|
||||
? AUTH_TRACKER_EVENTS.sign_in_with_password
|
||||
: AUTH_TRACKER_EVENTS.sign_up_with_password,
|
||||
payload: {
|
||||
email: passwordFormData.email,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" />
|
||||
<input type="hidden" value={passwordFormData.email} name="email" />
|
||||
@@ -292,6 +304,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
{isSMTPConfigured && (
|
||||
<Button
|
||||
type="button"
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_WITH_UNIQUE_CODE}
|
||||
onClick={redirectToUniqueCodeSignIn}
|
||||
variant="outline-primary"
|
||||
className="w-full"
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CircleCheck, XCircle } from "lucide-react";
|
||||
import { API_BASE_URL, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { API_BASE_URL, AUTH_TRACKER_ELEMENTS, AUTH_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { EAuthModes } from "@/helpers/authentication.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
@@ -38,8 +38,6 @@ const defaultValues: TUniqueCodeFormValues = {
|
||||
|
||||
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
// derived values
|
||||
const defaultResetTimerValue = 5;
|
||||
// states
|
||||
@@ -62,10 +60,22 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
setResendCodeTimer(defaultResetTimerValue);
|
||||
handleFormChange("code", uniqueCode?.code || "");
|
||||
setIsRequestingNewCode(false);
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.new_code_requested,
|
||||
payload: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setResendCodeTimer(0);
|
||||
console.error("Error while requesting new code");
|
||||
setIsRequestingNewCode(false);
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.new_code_requested,
|
||||
payload: {
|
||||
email: email,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,12 +94,23 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
||||
onSubmit={() => {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(AUTH_TRACKER_EVENTS.code_verify, {
|
||||
state: "SUCCESS",
|
||||
first_time: !isExistingEmail,
|
||||
captureSuccess({
|
||||
eventName: AUTH_TRACKER_EVENTS.code_verify,
|
||||
payload: {
|
||||
state: "SUCCESS",
|
||||
first_time: !isExistingEmail,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={() => {
|
||||
setIsSubmitting(false);
|
||||
captureError({
|
||||
eventName: AUTH_TRACKER_EVENTS.code_verify,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={() => setIsSubmitting(false)}
|
||||
>
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
|
||||
@@ -145,6 +166,7 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.REQUEST_NEW_CODE}
|
||||
onClick={() => generateNewCode(uniqueCodeFormData.email)}
|
||||
className={
|
||||
isRequestNewCodeDisabled
|
||||
@@ -163,7 +185,14 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
size="lg"
|
||||
disabled={isButtonDisabled}
|
||||
data-ph-element={AUTH_TRACKER_ELEMENTS.VERIFY_CODE}
|
||||
>
|
||||
{isRequestingNewCode ? (
|
||||
t("auth.common.unique_code.sending_code")
|
||||
) : isSubmitting ? (
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { Command } from "cmdk";
|
||||
import { ContrastIcon, FileText, Layers } from "lucide-react";
|
||||
// hooks
|
||||
import { CYCLE_TRACKER_ELEMENTS, MODULE_TRACKER_ELEMENTS, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { DiceIcon } from "@plane/ui";
|
||||
import { useCommandPalette, useEventTracker } from "@/hooks/store";
|
||||
// hooks
|
||||
import { useCommandPalette } from "@/hooks/store";
|
||||
// ui
|
||||
|
||||
type Props = {
|
||||
@@ -16,15 +18,14 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
// store hooks
|
||||
const { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal } =
|
||||
useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Cycle">
|
||||
<Command.Item
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
@@ -38,9 +39,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
</Command.Group>
|
||||
<Command.Group heading="Module">
|
||||
<Command.Item
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.COMMAND_PALETTE_ADD_ITEM}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
@@ -56,7 +57,6 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreateViewModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
@@ -70,9 +70,9 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
</Command.Group>
|
||||
<Command.Group heading="Page">
|
||||
<Command.Item
|
||||
data-ph-element={PROJECT_PAGE_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON}
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
||||
@@ -8,7 +8,12 @@ import useSWR from "swr";
|
||||
import { CommandIcon, FolderPlus, Search, Settings, X } from "lucide-react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_DEFAULT_SEARCH_RESULT } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
PROJECT_TRACKER_ELEMENTS,
|
||||
WORKSPACE_DEFAULT_SEARCH_RESULT,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceSearchResults } from "@plane/types";
|
||||
import { LayersIcon, Loader, ToggleSwitch } from "@plane/ui";
|
||||
@@ -28,6 +33,7 @@ import {
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import {
|
||||
useCommandPalette,
|
||||
useEventTracker,
|
||||
@@ -364,7 +370,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("Command palette");
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.COMMAND_PALETTE_CREATE_BUTTON });
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ArrowRight, ChevronRight } from "lucide-react";
|
||||
// Plane Imports
|
||||
import { CYCLE_TRACKER_EVENTS, CYCLE_STATUS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
CYCLE_STATUS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle } from "@plane/types";
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
@@ -13,13 +19,11 @@ import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||
// components
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useUserPermissions } from "@/hooks/store";
|
||||
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||
// services
|
||||
import { CycleService } from "@/services/cycle.service";
|
||||
// local imports
|
||||
import { ArchiveCycleModal } from "../archived-cycles";
|
||||
import { CycleDeleteModal } from "../delete-modal";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
@@ -38,13 +42,9 @@ const cycleService = new CycleService();
|
||||
|
||||
export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycleDetails, handleClose, isArchived = false } = props;
|
||||
// states
|
||||
const [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
||||
// hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { updateCycleDetails } = useCycle();
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { t } = useTranslation();
|
||||
const { renderFormattedDateInUserTimezone, getProjectUTCOffset } = useTimeZoneConverter(projectId);
|
||||
|
||||
@@ -61,29 +61,36 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
|
||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||
|
||||
const submitChanges = async (data: Partial<ICycle>, changedProperty: string) => {
|
||||
const submitChanges = async (data: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId || !cycleDetails.id) return;
|
||||
|
||||
await updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
||||
.then((res) => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
changed_properties: [changedProperty],
|
||||
element: "Right side-peek",
|
||||
.then(() => {
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
state: "SUCCESS",
|
||||
payload: {
|
||||
id: cycleDetails.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
.catch(() => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...data,
|
||||
element: "Right side-peek",
|
||||
state: "FAILED",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
state: "ERROR",
|
||||
payload: {
|
||||
id: cycleDetails.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -122,7 +129,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
isDateValid = true;
|
||||
}
|
||||
if (isDateValid) {
|
||||
submitChanges(payload, "date_range");
|
||||
submitChanges(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("project_cycles.action.update.success.title"),
|
||||
@@ -145,24 +152,6 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycleDetails && workspaceSlug && projectId && (
|
||||
<>
|
||||
<ArchiveCycleModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
cycleId={cycleDetails.id}
|
||||
isOpen={archiveCycleModal}
|
||||
handleClose={() => setArchiveCycleModal(false)}
|
||||
/>
|
||||
<CycleDeleteModal
|
||||
cycle={cycleDetails}
|
||||
isOpen={cycleDeleteModal}
|
||||
handleClose={() => setCycleDeleteModal(false)}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="sticky z-10 top-0 pt-2 flex items-center justify-between bg-custom-sidebar-background-100">
|
||||
<div className="flex items-center justify-center size-5">
|
||||
<button
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { useState, Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { CYCLE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
@@ -42,16 +44,28 @@ export const ArchiveCycleModal: React.FC<Props> = (props) => {
|
||||
title: "Archive success",
|
||||
message: "Your archives can be found in project archives.",
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
onClose();
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Cycle could not be archived. Please try again.",
|
||||
})
|
||||
)
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.finally(() => setIsArchiving(false));
|
||||
};
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useCycle } from "@/hooks/store";
|
||||
import { useCycle } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
interface ICycleDelete {
|
||||
@@ -27,7 +28,6 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
// store hooks
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { deleteCycle } = useCycle();
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
@@ -49,9 +49,11 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||
payload: { ...cycle, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: cycle.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
@@ -64,13 +66,16 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.delete,
|
||||
payload: { ...cycle, state: "FAILED" },
|
||||
payload: {
|
||||
id: cycle.id,
|
||||
},
|
||||
error: errors,
|
||||
});
|
||||
})
|
||||
.finally(() => handleClose());
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Warning!",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
|
||||
import { useUser } from "@/hooks/store/user/user-user";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
|
||||
handleFormSubmit: (values: Partial<ICycle>) => Promise<void>;
|
||||
handleClose: () => void;
|
||||
status: boolean;
|
||||
projectId: string;
|
||||
@@ -40,7 +40,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||
const { projectsWithCreatePermissions } = useUser();
|
||||
// form data
|
||||
const {
|
||||
formState: { errors, isSubmitting, dirtyFields },
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
@@ -64,7 +64,7 @@ export const CycleForm: React.FC<Props> = (props) => {
|
||||
}, [data, reset]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
|
||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData))}>
|
||||
<div className="space-y-5 p-5">
|
||||
<div className="flex items-center gap-x-3">
|
||||
{!status && (
|
||||
|
||||
@@ -6,7 +6,13 @@ import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Eye, Users, ArrowRight, CalendarDays } from "lucide-react";
|
||||
// types
|
||||
import { CYCLE_TRACKER_EVENTS, EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, TCycleGroups } from "@plane/types";
|
||||
@@ -19,7 +25,8 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { MergedDateDisplay } from "@/components/dropdowns/merged-date";
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { useTimeZoneConverter } from "@/hooks/use-timezone-converter";
|
||||
@@ -57,7 +64,6 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { addCycleToFavorites, removeCycleFromFavorites } = useCycle();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// local storage
|
||||
@@ -98,16 +104,25 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId).then(
|
||||
() => {
|
||||
const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||
.then(() => {
|
||||
if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
|
||||
captureEvent(CYCLE_TRACKER_EVENTS.favorite, {
|
||||
cycle_id: cycleId,
|
||||
element: "List layout",
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.favorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(addToFavoritePromise, {
|
||||
loading: t("project_cycles.action.favorite.loading"),
|
||||
@@ -126,17 +141,24 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const removeFromFavoritePromise = removeCycleFromFavorites(
|
||||
workspaceSlug?.toString(),
|
||||
projectId.toString(),
|
||||
cycleId
|
||||
).then(() => {
|
||||
captureEvent(CYCLE_TRACKER_EVENTS.unfavorite, {
|
||||
cycle_id: cycleId,
|
||||
element: "List layout",
|
||||
state: "SUCCESS",
|
||||
const removeFromFavoritePromise = removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycleId)
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.unfavorite,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(removeFromFavoritePromise, {
|
||||
loading: t("project_cycles.action.unfavorite.loading"),
|
||||
@@ -292,6 +314,7 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
)}
|
||||
{isEditingAllowed && !cycleDetails.archived_at && (
|
||||
<FavoriteStar
|
||||
data-ph-element={CYCLE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -12,7 +12,8 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
||||
import { CycleForm } from "@/components/cycles";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useCycle, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useProject } from "@/hooks/store";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
@@ -35,7 +36,6 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
// states
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
// store hooks
|
||||
const { captureCycleEvent } = useEventTracker();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createCycle, updateCycleDetails } = useCycle();
|
||||
const { isMobile } = usePlatformOS();
|
||||
@@ -63,9 +63,11 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
title: "Success!",
|
||||
message: "Cycle created successfully.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
||||
payload: { ...res, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: res.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -74,23 +76,24 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
title: "Error!",
|
||||
message: err?.detail ?? "Error in creating cycle. Please try again.",
|
||||
});
|
||||
captureCycleEvent({
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.create,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>, dirtyFields: any) => {
|
||||
const handleUpdateCycle = async (cycleId: string, payload: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||
await updateCycleDetails(workspaceSlug, selectedProjectId, cycleId, payload)
|
||||
.then((res) => {
|
||||
const changed_properties = Object.keys(dirtyFields);
|
||||
captureCycleEvent({
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: changed_properties, state: "SUCCESS" },
|
||||
payload: {
|
||||
id: res.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -99,15 +102,15 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, state: "FAILED" },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.detail ?? "Error in updating cycle. Please try again.",
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.update,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -121,7 +124,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
return status;
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<ICycle>, dirtyFields: any) => {
|
||||
const handleFormSubmit = async (formData: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload: Partial<ICycle> = {
|
||||
@@ -145,7 +148,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
}
|
||||
|
||||
if (isDateValid) {
|
||||
if (data) await handleUpdateCycle(data.id, payload, dirtyFields);
|
||||
if (data) await handleUpdateCycle(data.id, payload);
|
||||
else {
|
||||
await handleCreateCycle(payload).then(() => {
|
||||
setCycleTab("all");
|
||||
|
||||
@@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
CYCLE_TRACKER_EVENTS,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||
@@ -14,7 +19,8 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||
import { ArchiveCycleModal, CycleCreateUpdateModal, CycleDeleteModal } from "@/components/cycles";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useCycle, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick, captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useCycle, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useEndCycle, EndCycleModal } from "@/plane-web/components/cycles";
|
||||
|
||||
@@ -35,7 +41,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
const [archiveCycleModal, setArchiveCycleModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { getCycleById, restoreCycle } = useCycle();
|
||||
const { t } = useTranslation();
|
||||
@@ -69,7 +74,6 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
|
||||
|
||||
const handleEditCycle = () => {
|
||||
setTrackElement("Cycles page list layout");
|
||||
setUpdateModal(true);
|
||||
};
|
||||
|
||||
@@ -83,18 +87,29 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
title: t("project_cycles.action.restore.success.title"),
|
||||
message: t("project_cycles.action.restore.success.description"),
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/cycles`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("project_cycles.action.restore.failed.title"),
|
||||
message: t("project_cycles.action.restore.failed.description"),
|
||||
})
|
||||
);
|
||||
});
|
||||
captureError({
|
||||
eventName: CYCLE_TRACKER_EVENTS.restore,
|
||||
payload: {
|
||||
id: cycleId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const handleDeleteCycle = () => {
|
||||
setTrackElement("Cycles page list layout");
|
||||
setDeleteModal(true);
|
||||
};
|
||||
|
||||
@@ -149,6 +164,16 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
|
||||
if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu);
|
||||
|
||||
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{cycleDetails && (
|
||||
@@ -187,7 +212,7 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect maxHeight="lg" buttonClassName={customClassName}>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
@@ -197,6 +222,9 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: CYCLE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||
});
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { TourRoot } from "@/components/onboarding";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useUserProfile, useEventTracker, useUser } from "@/hooks/store";
|
||||
import { useUserProfile, useUser } from "@/hooks/store";
|
||||
import { useHome } from "@/hooks/store/use-home";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
// plane web components
|
||||
import { HomePeekOverviewsRoot } from "@/plane-web/components/home";
|
||||
// local imports
|
||||
import { DashboardWidgets } from "./home-dashboard-widgets";
|
||||
import { UserGreetingsView } from "./user-greetings";
|
||||
|
||||
@@ -21,7 +24,6 @@ export const WorkspaceHomeView = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const { data: currentUser } = useUser();
|
||||
const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { toggleWidgetSettings, fetchWidgets } = useHome();
|
||||
const [windowWidth] = useSize();
|
||||
|
||||
@@ -38,9 +40,11 @@ export const WorkspaceHomeView = observer(() => {
|
||||
const handleTourCompleted = () => {
|
||||
updateTourCompleted()
|
||||
.then(() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.complete, {
|
||||
user_id: currentUser?.id,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: PRODUCT_TOUR_TRACKER_EVENTS.complete,
|
||||
payload: {
|
||||
user_id: currentUser?.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -5,12 +5,13 @@ import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Briefcase, Check, Hotel, Users, X } from "lucide-react";
|
||||
// plane ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import {
|
||||
useCommandPalette,
|
||||
useEventTracker,
|
||||
@@ -61,6 +62,7 @@ export const NoProjectsEmptyState = observer(() => {
|
||||
e.stopPropagation();
|
||||
setTrackElement("Sidebar");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
},
|
||||
disabled: !canCreateProject,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import { Plus } from "lucide-react";
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IJiraImporterForm } from "@plane/types";
|
||||
// hooks
|
||||
// components
|
||||
@@ -201,6 +202,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Jira import detail page");
|
||||
toggleCreateProjectModal(true);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EIssuesStoreType, EUserPermissionsLevel, EUserWorkspaceRoles } from "@plane/constants";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
EUserPermissionsLevel,
|
||||
EUserWorkspaceRoles,
|
||||
PROJECT_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
@@ -47,6 +53,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
onClick={() => {
|
||||
setTrackElement("All issues empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
|
||||
+21
-11
@@ -13,7 +13,7 @@ import {
|
||||
EViewAccess,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
GLOBAL_VIEW_TOUR_TRACKER_EVENTS,
|
||||
GLOBAL_VIEW_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
||||
//ui
|
||||
@@ -26,7 +26,8 @@ import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView, useIssues, useLabel, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { getAreFiltersEqual } from "../../../utils";
|
||||
|
||||
type Props = {
|
||||
@@ -44,7 +45,6 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||
const { workspaceLabels } = useLabel();
|
||||
const { globalViewMap, updateGlobalView } = useGlobalView();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { data } = useUser();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
@@ -112,15 +112,25 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
||||
const handleUpdateView = () => {
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
|
||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters).then((res) => {
|
||||
if (res)
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, {
|
||||
view_id: res.id,
|
||||
applied_filters: res.filters,
|
||||
state: "SUCCESS",
|
||||
element: "Spreadsheet view",
|
||||
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), viewFilters)
|
||||
.then((res) => {
|
||||
if (res)
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// add a placeholder object instead of appliedFilters if it is undefined
|
||||
|
||||
@@ -4,12 +4,13 @@ import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EDraftIssuePaginationType } from "@plane/constants";
|
||||
import { EDraftIssuePaginationType, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserWorkspaceRoles } from "@plane/constants/src/user";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { cn } from "@plane/utils";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// constants
|
||||
|
||||
// helpers
|
||||
@@ -76,6 +77,7 @@ export const WorkspaceDraftIssuesRoot: FC<TWorkspaceDraftIssuesRoot> = observer(
|
||||
description={t("workspace_projects.empty_state.no_projects.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!hasMemberLevelPermission}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
EUserPermissionsLevel,
|
||||
EEstimateSystem,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
// plane types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
@@ -22,15 +23,10 @@ import { Loader, LayersIcon, CustomSelect, ModuleStatusIcon, TOAST_TYPE, setToas
|
||||
// helpers
|
||||
import { getDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||
import {
|
||||
ArchiveModuleModal,
|
||||
DeleteModuleModal,
|
||||
CreateUpdateModuleLinkModal,
|
||||
ModuleAnalyticsProgress,
|
||||
ModuleLinksList,
|
||||
} from "@/components/modules";
|
||||
import { CreateUpdateModuleLinkModal, ModuleAnalyticsProgress, ModuleLinksList } from "@/components/modules";
|
||||
import { captureElementAndEvent, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useModule, useEventTracker, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
||||
import { useModule, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
const defaultValues: Partial<IModule> = {
|
||||
lead_id: "",
|
||||
@@ -50,8 +46,6 @@ type Props = {
|
||||
export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { moduleId, handleClose, isArchived } = props;
|
||||
// states
|
||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
||||
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
||||
// router
|
||||
@@ -62,7 +56,6 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
|
||||
const { captureModuleEvent, captureEvent } = useEventTracker();
|
||||
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();
|
||||
|
||||
// derived values
|
||||
@@ -79,15 +72,22 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||
updateModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), data)
|
||||
.then((res) => {
|
||||
captureModuleEvent({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.RIGHT_SIDEBAR,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { id: res.id },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureModuleEvent({
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -97,12 +97,20 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
|
||||
const payload = { metadata: {}, ...formData };
|
||||
|
||||
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload).then(() =>
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.create, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
})
|
||||
);
|
||||
await createModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), payload)
|
||||
.then(() =>
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.create,
|
||||
payload: { id: moduleId },
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.create,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateLink = async (formData: ModuleLink, linkId: string) => {
|
||||
@@ -110,13 +118,20 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
|
||||
const payload = { metadata: {}, ...formData };
|
||||
|
||||
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload).then(
|
||||
() =>
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.update, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
await updateModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId, payload)
|
||||
.then(() =>
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||
payload: { id: moduleId },
|
||||
})
|
||||
);
|
||||
)
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.update,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteLink = async (linkId: string) => {
|
||||
@@ -124,9 +139,9 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
|
||||
deleteModuleLink(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), linkId)
|
||||
.then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.link.delete, {
|
||||
module_id: moduleId,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.delete,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -140,6 +155,10 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "Some error occurred",
|
||||
});
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.link.delete,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -213,16 +232,6 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
createLink={handleCreateLink}
|
||||
updateLink={handleUpdateLink}
|
||||
/>
|
||||
{workspaceSlug && projectId && (
|
||||
<ArchiveModuleModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
moduleId={moduleId}
|
||||
isOpen={archiveModuleModal}
|
||||
handleClose={() => setArchiveModuleModal(false)}
|
||||
/>
|
||||
)}
|
||||
<DeleteModuleModal isOpen={moduleDeleteModal} onClose={() => setModuleDeleteModal(false)} data={moduleDetails} />
|
||||
<>
|
||||
<div
|
||||
className={`sticky z-10 top-0 flex items-center justify-between bg-custom-sidebar-background-100 pb-5 pt-5`}
|
||||
|
||||
@@ -10,8 +10,10 @@ import type { IModule } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useModule } from "@/hooks/store";
|
||||
import { useModule } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
@@ -28,7 +30,6 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, moduleId, peekModule } = useParams();
|
||||
// store hooks
|
||||
const { captureModuleEvent } = useEventTracker();
|
||||
const { deleteModule } = useModule();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -51,9 +52,9 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||
title: "Success!",
|
||||
message: "Module deleted successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||
payload: { ...data, state: "SUCCESS" },
|
||||
payload: { id: data.id },
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
@@ -66,9 +67,10 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: currentError.i18n_message && t(currentError.i18n_message),
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.delete,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data.id },
|
||||
error: errors,
|
||||
});
|
||||
})
|
||||
.finally(() => handleClose());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { Copy, Pencil, Trash2 } from "lucide-react";
|
||||
// plane types
|
||||
import { MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { ILinkDetails } from "@plane/types";
|
||||
// plane ui
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
@@ -58,6 +59,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -77,6 +79,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center p-1 hover:bg-custom-background-80"
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.LIST_ITEM}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -11,8 +11,10 @@ import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@p
|
||||
// components
|
||||
import { ModuleForm } from "@/components/modules";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useModule, useProject } from "@/hooks/store";
|
||||
import { useModule, useProject } from "@/hooks/store";
|
||||
import useKeypress from "@/hooks/use-keypress";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
@@ -37,7 +39,6 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
// store hooks
|
||||
const { captureModuleEvent } = useEventTracker();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
const { createModule, updateModuleDetails } = useModule();
|
||||
const { isMobile } = usePlatformOS();
|
||||
@@ -63,9 +64,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||
title: "Success!",
|
||||
message: "Module created successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.create,
|
||||
payload: { ...res, state: "SUCCESS" },
|
||||
payload: { id: res.id },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -74,14 +75,15 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: err?.detail ?? err?.error ?? "Module could not be created. Please try again.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.create,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data?.id },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateModule = async (payload: Partial<IModule>, dirtyFields: any) => {
|
||||
const handleUpdateModule = async (payload: Partial<IModule>) => {
|
||||
if (!workspaceSlug || !projectId || !data) return;
|
||||
|
||||
const selectedProjectId = payload.project_id ?? projectId.toString();
|
||||
@@ -94,9 +96,9 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||
title: "Success!",
|
||||
message: "Module updated successfully.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...res, changed_properties: Object.keys(dirtyFields || {}), state: "SUCCESS" },
|
||||
payload: { id: res.id },
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -105,21 +107,22 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: err?.detail ?? err?.error ?? "Module could not be updated. Please try again.",
|
||||
});
|
||||
captureModuleEvent({
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.update,
|
||||
payload: { ...data, state: "FAILED" },
|
||||
payload: { id: data.id },
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<IModule>, dirtyFields: unknown) => {
|
||||
const handleFormSubmit = async (formData: Partial<IModule>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload: Partial<IModule> = {
|
||||
...formData,
|
||||
};
|
||||
if (!data) await handleCreateModule(payload);
|
||||
else await handleUpdateModule(payload, dirtyFields);
|
||||
else await handleUpdateModule(payload);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { IModule } from "@plane/types";
|
||||
@@ -33,8 +34,9 @@ import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
// helpers
|
||||
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web constants
|
||||
@@ -56,7 +58,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
// local storage
|
||||
const { setValue: toggleFavoriteMenu, storedValue } = useLocalStorage<boolean>(IS_FAVORITE_MENU_OPEN, false);
|
||||
@@ -79,10 +80,15 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then(
|
||||
() => {
|
||||
if (!storedValue) toggleFavoriteMenu(true);
|
||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -110,10 +116,15 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
projectId.toString(),
|
||||
moduleId
|
||||
).then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CARD_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
EUserPermissionsLevel,
|
||||
IS_FAVORITE_MENU_OPEN,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
@@ -24,8 +25,10 @@ import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
// constants
|
||||
// helpers
|
||||
import { captureElementAndEvent, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { ButtonAvatars } from "../dropdowns/member/avatar";
|
||||
|
||||
type Props = {
|
||||
@@ -42,7 +45,6 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -64,17 +66,28 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||
e.preventDefault();
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId).then(
|
||||
() => {
|
||||
const addToFavoritePromise = addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), moduleId)
|
||||
.then(() => {
|
||||
// open favorites menu if closed
|
||||
if (!storedValue) toggleFavoriteMenu(true);
|
||||
captureEvent(MODULE_TRACKER_EVENTS.favorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.favorite,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(addToFavoritePromise, {
|
||||
loading: "Adding module to favorites...",
|
||||
@@ -98,13 +111,26 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleId
|
||||
).then(() => {
|
||||
captureEvent(MODULE_TRACKER_EVENTS.unfavorite, {
|
||||
module_id: moduleId,
|
||||
element: "Grid layout",
|
||||
state: "SUCCESS",
|
||||
)
|
||||
.then(() => {
|
||||
captureElementAndEvent({
|
||||
element: {
|
||||
elementName: MODULE_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
},
|
||||
event: {
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.unfavorite,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setPromiseToast(removeFromFavoritePromise, {
|
||||
loading: "Removing module from favorites...",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ContentWrapper, Row, ERowVariant } from "@plane/ui";
|
||||
import { ListLayout } from "@/components/core/list";
|
||||
@@ -10,7 +10,7 @@ import { DetailedEmptyState, ComicBoxButton } from "@/components/empty-state";
|
||||
import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "@/components/modules";
|
||||
import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from "@/components/ui";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useModule, useModuleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useModule, useModuleFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import AllFiltersImage from "@/public/empty-state/module/all-filters.svg";
|
||||
import NameFilterImage from "@/public/empty-state/module/name-filter.svg";
|
||||
@@ -24,7 +24,6 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateModuleModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { getProjectModuleIds, getFilteredModuleIds, loader } = useModule();
|
||||
const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
@@ -59,8 +58,8 @@ export const ModulesListView: React.FC = observer(() => {
|
||||
label={t("project_module.empty_state.general.primary_button.text")}
|
||||
title={t("project_module.empty_state.general.primary_button.comic.title")}
|
||||
description={t("project_module.empty_state.general.primary_button.comic.description")}
|
||||
data-ph-element={MODULE_TRACKER_ELEMENTS.EMPTY_STATE_ADD_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Module empty state");
|
||||
toggleCreateModuleModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
|
||||
@@ -6,7 +6,12 @@ import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
MODULE_TRACKER_ELEMENTS,
|
||||
MODULE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
@@ -14,8 +19,9 @@ import { copyUrlToClipboard, cn } from "@plane/utils";
|
||||
// components
|
||||
import { ArchiveModuleModal, CreateUpdateModuleModal, DeleteModuleModal } from "@/components/modules";
|
||||
// helpers
|
||||
import { captureClick, captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useModule, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useModule, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
@@ -35,7 +41,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
const [archiveModuleModal, setArchiveModuleModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { getModuleById, restoreModule } = useModule();
|
||||
@@ -67,7 +72,6 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
const handleOpenInNewTab = () => window.open(`/${moduleLink}`, "_blank");
|
||||
|
||||
const handleEditModule = () => {
|
||||
setTrackElement("Modules page list layout");
|
||||
setEditModal(true);
|
||||
};
|
||||
|
||||
@@ -81,18 +85,26 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
title: "Restore success",
|
||||
message: "Your module can be found in project modules.",
|
||||
});
|
||||
captureSuccess({
|
||||
eventName: MODULE_TRACKER_EVENTS.restore,
|
||||
payload: { id: moduleId },
|
||||
});
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/archives/modules`);
|
||||
})
|
||||
.catch(() =>
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Module could not be restored. Please try again.",
|
||||
})
|
||||
);
|
||||
});
|
||||
captureError({
|
||||
eventName: MODULE_TRACKER_EVENTS.restore,
|
||||
payload: { id: moduleId },
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
const handleDeleteModule = () => {
|
||||
setTrackElement("Modules page list layout");
|
||||
setDeleteModal(true);
|
||||
};
|
||||
|
||||
@@ -145,6 +157,16 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
},
|
||||
];
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
onClick: () => {
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{moduleDetails && (
|
||||
@@ -166,7 +188,7 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
<DeleteModuleModal data={moduleDetails} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
||||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
@@ -176,6 +198,9 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||
});
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
||||
@@ -4,19 +4,15 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// constants
|
||||
import {
|
||||
ONBOARDING_TRACKER_EVENTS,
|
||||
ORGANIZATION_SIZE,
|
||||
RESTRICTED_URLS,
|
||||
WORKSPACE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS, WORKSPACE_TRACKER_EVENTS } from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUser, IWorkspace, TOnboardingSteps } from "@plane/types";
|
||||
// ui
|
||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserProfile, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
||||
@@ -41,7 +37,6 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||
const { updateUserProfile } = useUserProfile();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
const { createWorkspace, fetchWorkspaces } = useWorkspace();
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -73,26 +68,18 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||
title: t("workspace_creation.toast.success.title"),
|
||||
message: t("workspace_creation.toast.success.message"),
|
||||
});
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...workspaceResponse,
|
||||
state: "SUCCESS",
|
||||
first_time: true,
|
||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
await completeStep(workspaceResponse.id);
|
||||
})
|
||||
.catch(() => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
first_time: true,
|
||||
element: ONBOARDING_TRACKER_EVENTS.root,
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
error: new Error("Error creating workspace"),
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -290,7 +277,14 @@ export const CreateWorkspace: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="primary" type="submit" size="lg" className="w-full" disabled={isButtonDisabled}>
|
||||
<Button
|
||||
data-ph-element="ONBOARDING_CREATE_WORKSPACE_BUTTON"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("workspace_creation.button.default")}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
// plane imports
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// types
|
||||
import { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Checkbox, Spinner } from "@plane/ui";
|
||||
import { truncateText, getUserRole } from "@plane/utils";
|
||||
import { truncateText } from "@plane/utils";
|
||||
// constants
|
||||
// helpers
|
||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||
// hooks
|
||||
import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
|
||||
@@ -29,7 +30,6 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { fetchWorkspaces } = useWorkspace();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
|
||||
@@ -50,26 +50,23 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||
|
||||
try {
|
||||
await workspaceService.joinWorkspaces({ invitations: invitationsRespond });
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "SUCCESS",
|
||||
element: "Workspace invitations page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
},
|
||||
});
|
||||
await fetchWorkspaces();
|
||||
await fetchCurrentUserSettings();
|
||||
await handleNextStep();
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.accept, {
|
||||
member_id: invitation?.id,
|
||||
role: getUserRole(invitation?.role as any),
|
||||
project_id: undefined,
|
||||
accepted_from: "App",
|
||||
state: "FAILED",
|
||||
element: "Workspace invitations page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.accept,
|
||||
payload: {
|
||||
member_id: invitation?.id,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
setIsJoiningWorkspaces(false);
|
||||
}
|
||||
@@ -117,6 +114,7 @@ export const Invitations: React.FC<Props> = (props) => {
|
||||
className="w-full"
|
||||
onClick={submitInvitations}
|
||||
disabled={isJoiningWorkspaces || !invitationsRespond.length}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_JOIN_WORKSPACE}
|
||||
>
|
||||
{isJoiningWorkspaces ? <Spinner height="20px" width="20px" /> : "Continue to workspace"}
|
||||
</Button>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IUser, IWorkspace } from "@plane/types";
|
||||
@@ -28,9 +28,8 @@ import { IUser, IWorkspace } from "@plane/types";
|
||||
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
import { getUserRole } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
// assets
|
||||
@@ -276,8 +275,6 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
|
||||
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -311,16 +308,11 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
})),
|
||||
})
|
||||
.then(async () => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
emails: [
|
||||
...payload.emails.map((email) => ({
|
||||
email: email.email,
|
||||
role: getUserRole(email.role),
|
||||
})),
|
||||
],
|
||||
project_id: undefined,
|
||||
state: "SUCCESS",
|
||||
element: "Onboarding",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
workspace: workspace.slug,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -331,10 +323,12 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
await nextStep();
|
||||
})
|
||||
.catch((err) => {
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.invite, {
|
||||
project_id: undefined,
|
||||
state: "FAILED",
|
||||
element: "Onboarding",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
workspace: workspace.slug,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -426,6 +420,7 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
size="lg"
|
||||
className="w-full"
|
||||
disabled={isInvitationDisabled || !isValid || isSubmitting}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.ONBOARDING_INVITE_MEMBER}
|
||||
>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_EVENTS, USER_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { E_PASSWORD_STRENGTH, ONBOARDING_TRACKER_ELEMENTS, USER_TRACKER_EVENTS } from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUser, TUserProfile, TOnboardingSteps } from "@plane/types";
|
||||
@@ -20,7 +20,8 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useUser, useUserProfile } from "@/hooks/store";
|
||||
import { captureError, captureSuccess, captureView } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useUserProfile } from "@/hooks/store";
|
||||
// assets
|
||||
import ProfileSetupDark from "@/public/onboarding/profile-setup-dark.webp";
|
||||
import ProfileSetupLight from "@/public/onboarding/profile-setup-light.webp";
|
||||
@@ -98,7 +99,6 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
// store hooks
|
||||
const { updateCurrentUser } = useUser();
|
||||
const { updateUserProfile } = useUserProfile();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// form info
|
||||
const {
|
||||
getValues,
|
||||
@@ -143,11 +143,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
updateUserProfile(profileUpdatePayload),
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -159,9 +160,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
finishOnboarding();
|
||||
}
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -183,9 +183,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
formData.password && handleSetPassword(formData.password),
|
||||
]).then(() => setProfileSetupStep(EProfileSetupSteps.USER_PERSONALIZATION));
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_1,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -205,11 +204,12 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
updateUserProfile(profileUpdatePayload),
|
||||
totalSteps > 2 && stepChange({ profile_complete: true }),
|
||||
]);
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
state: "SUCCESS",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
||||
captureSuccess({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
payload: {
|
||||
use_case: formData.use_case,
|
||||
role: formData.role,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -221,9 +221,8 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
finishOnboarding();
|
||||
}
|
||||
} catch {
|
||||
captureEvent(USER_TRACKER_EVENTS.add_details, {
|
||||
state: "FAILED",
|
||||
element: ONBOARDING_TRACKER_EVENTS.step_2,
|
||||
captureError({
|
||||
eventName: USER_TRACKER_EVENTS.add_details,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -235,6 +234,9 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
|
||||
const onSubmit = async (formData: TProfileSetupFormValues) => {
|
||||
if (!user) return;
|
||||
captureView({
|
||||
elementName: ONBOARDING_TRACKER_ELEMENTS.PROFILE_SETUP_FORM,
|
||||
});
|
||||
if (profileSetupStep === EProfileSetupSteps.ALL) await handleSubmitProfileSetup(formData);
|
||||
if (profileSetupStep === EProfileSetupSteps.USER_DETAILS) await handleSubmitUserDetail(formData);
|
||||
if (profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION) await handleSubmitUserPersonalization(formData);
|
||||
|
||||
@@ -5,13 +5,14 @@ import { observer } from "mobx-react";
|
||||
import Image, { StaticImageData } from "next/image";
|
||||
import { X } from "lucide-react";
|
||||
// ui
|
||||
import { PRODUCT_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { PRODUCT_TOUR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { TourSidebar } from "@/components/onboarding";
|
||||
// constants
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUser } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useUser } from "@/hooks/store";
|
||||
// assets
|
||||
import CyclesTour from "@/public/onboarding/cycles.webp";
|
||||
import IssuesTour from "@/public/onboarding/issues.webp";
|
||||
@@ -85,7 +86,6 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement, captureEvent } = useEventTracker();
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||
@@ -112,7 +112,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.start);
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.START_BUTTON,
|
||||
});
|
||||
setStep("work-items");
|
||||
}}
|
||||
>
|
||||
@@ -122,7 +124,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
type="button"
|
||||
className="bg-transparent text-xs font-medium text-custom-primary-100 outline-custom-text-100"
|
||||
onClick={() => {
|
||||
captureEvent(PRODUCT_TOUR_TRACKER_EVENTS.skip);
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.SKIP_BUTTON,
|
||||
});
|
||||
onComplete();
|
||||
}}
|
||||
>
|
||||
@@ -171,7 +175,9 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setTrackElement("Product tour");
|
||||
captureClick({
|
||||
elementName: PRODUCT_TOUR_TRACKER_ELEMENTS.CREATE_PROJECT_BUTTON,
|
||||
});
|
||||
onComplete();
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
// constants
|
||||
import { EPageAccess } from "@plane/constants";
|
||||
import { EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// plane editor
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// plane ui
|
||||
@@ -26,6 +26,7 @@ import { cn } from "@plane/utils";
|
||||
import { DeletePageModal } from "@/components/pages";
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// plane web components
|
||||
import { MovePageModal } from "@/plane-web/components/pages";
|
||||
@@ -92,14 +93,24 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
||||
const menuItems: (TContextMenuItem & { key: TPageActions })[] = [
|
||||
{
|
||||
key: "toggle-lock",
|
||||
action: pageOperations.toggleLock,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.LOCK_BUTTON,
|
||||
});
|
||||
pageOperations.toggleLock();
|
||||
},
|
||||
title: is_locked ? "Unlock" : "Lock",
|
||||
icon: is_locked ? LockKeyholeOpen : LockKeyhole,
|
||||
shouldRender: canCurrentUserLockPage,
|
||||
},
|
||||
{
|
||||
key: "toggle-access",
|
||||
action: pageOperations.toggleAccess,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.ACCESS_TOGGLE,
|
||||
});
|
||||
pageOperations.toggleAccess();
|
||||
},
|
||||
title: access === EPageAccess.PUBLIC ? "Make private" : "Make public",
|
||||
icon: access === EPageAccess.PUBLIC ? Lock : Globe2,
|
||||
shouldRender: canCurrentUserChangeAccess && !archived_at,
|
||||
@@ -120,21 +131,36 @@ export const PageActions: React.FC<Props> = observer((props) => {
|
||||
},
|
||||
{
|
||||
key: "make-a-copy",
|
||||
action: pageOperations.duplicate,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.DUPLICATE_BUTTON,
|
||||
});
|
||||
pageOperations.duplicate();
|
||||
},
|
||||
title: "Make a copy",
|
||||
icon: Copy,
|
||||
shouldRender: canCurrentUserDuplicatePage,
|
||||
},
|
||||
{
|
||||
key: "archive-restore",
|
||||
action: pageOperations.toggleArchive,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.ARCHIVE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleArchive();
|
||||
},
|
||||
title: archived_at ? "Restore" : "Archive",
|
||||
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
||||
shouldRender: canCurrentUserArchivePage,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => setDeletePageModal(true),
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
setDeletePageModal(true);
|
||||
},
|
||||
title: "Delete",
|
||||
icon: Trash2,
|
||||
shouldRender: canCurrentUserDeletePage && !!archived_at,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
// constants
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// ui
|
||||
import { FavoriteStar } from "@plane/ui";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// store
|
||||
@@ -23,7 +27,12 @@ export const PageFavoriteControl = observer(({ page }: Props) => {
|
||||
return (
|
||||
<FavoriteStar
|
||||
selected={is_favorite}
|
||||
onClick={pageOperations.toggleFavorite}
|
||||
onClick={() => {
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleFavorite();
|
||||
}}
|
||||
buttonClassName="flex-shrink-0 size-6 group rounded hover:bg-custom-background-80 transition-colors"
|
||||
iconClassName="size-3.5 text-custom-text-200 group-hover:text-custom-text-10"
|
||||
/>
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Earth, Info, Lock, Minus } from "lucide-react";
|
||||
// constants
|
||||
import { PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
// ui
|
||||
import { Avatar, FavoriteStar, Tooltip } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { PageActions } from "@/components/pages";
|
||||
// helpers
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
@@ -64,6 +67,9 @@ export const BlockItemAction: FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: PROJECT_PAGE_TRACKER_ELEMENTS.FAVORITE_BUTTON,
|
||||
});
|
||||
pageOperations.toggleFavorite();
|
||||
}}
|
||||
selected={is_favorite}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
// components
|
||||
import { PageForm } from "@/components/pages";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web hooks
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
@@ -42,7 +42,6 @@ export const CreatePageModal: FC<Props> = (props) => {
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { createPage } = usePageStore(storeType);
|
||||
const { capturePageEvent } = useEventTracker();
|
||||
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
|
||||
setPageFormData((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
@@ -62,22 +61,19 @@ export const CreatePageModal: FC<Props> = (props) => {
|
||||
try {
|
||||
const pageData = await createPage(pageFormData);
|
||||
if (pageData) {
|
||||
capturePageEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...pageData,
|
||||
state: "SUCCESS",
|
||||
id: pageData.id,
|
||||
},
|
||||
});
|
||||
handleStateClear();
|
||||
if (redirectionEnabled) router.push(`/${workspaceSlug}/projects/${projectId}/pages/${pageData.id}`);
|
||||
}
|
||||
} catch {
|
||||
capturePageEvent({
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
},
|
||||
error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PROJECT_PAGE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
// plane web hooks
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
@@ -28,7 +28,6 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
// store hooks
|
||||
const { removePage } = usePageStore(storeType);
|
||||
const { capturePageEvent } = useEventTracker();
|
||||
if (!page || !page.id) return null;
|
||||
// derived values
|
||||
const { id: pageId, name } = page;
|
||||
@@ -45,11 +44,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
||||
setIsDeleting(true);
|
||||
await removePage(pageId)
|
||||
.then(() => {
|
||||
capturePageEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...page,
|
||||
state: "SUCCESS",
|
||||
id: pageId,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
@@ -64,11 +62,10 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
capturePageEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...page,
|
||||
state: "FAILED",
|
||||
id: pageId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, EPageAccess } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserProjectRoles, EPageAccess, PROJECT_PAGE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
import { DetailedEmptyState } from "@/components/empty-state";
|
||||
import { PageLoader } from "@/components/pages";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// plane web hooks
|
||||
@@ -63,6 +64,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||
text: t("project_page.empty_state.general.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
@@ -79,6 +81,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||
text: t("project_page.empty_state.public.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
@@ -94,6 +97,7 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||
text: t("project_page.empty_state.private.primary_button.text"),
|
||||
onClick: () => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
||||
captureClick({ elementName: PROJECT_PAGE_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_BUTTON });
|
||||
},
|
||||
disabled: !canPerformEmptyStateActions,
|
||||
}}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EventProps, STATE_TRACKER_EVENTS, STATE_GROUPS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS, STATE_GROUPS } from "@plane/constants";
|
||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { StateForm } from "@/components/project-states";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
|
||||
type TStateCreate = {
|
||||
groupKey: TStateGroups;
|
||||
@@ -19,17 +19,10 @@ type TStateCreate = {
|
||||
|
||||
export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||
const { groupKey, shouldTrackEvents, createStateCallback, handleClose } = props;
|
||||
// hooks
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setLoader(false);
|
||||
handleClose();
|
||||
@@ -38,19 +31,16 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||
const onSubmit = async (formData: Partial<IState>) => {
|
||||
if (!groupKey) return { status: "error" };
|
||||
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
try {
|
||||
const stateResponse = await createStateCallback({ ...formData, group: groupKey });
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
const response = await createStateCallback({ ...formData, group: groupKey });
|
||||
if (shouldTrackEvents)
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state_group: groupKey,
|
||||
id: response.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
@@ -60,14 +50,13 @@ export const StateCreate: FC<TStateCreate> = observer((props) => {
|
||||
return { status: "success" };
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents)
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state_group: groupKey,
|
||||
},
|
||||
});
|
||||
if (errorStatus?.status === 400) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EventProps, STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { StateForm } from "@/components/project-states";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
|
||||
type TStateUpdate = {
|
||||
state: IState;
|
||||
@@ -19,8 +19,6 @@ type TStateUpdate = {
|
||||
|
||||
export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||
const { state, updateStateCallback, shouldTrackEvents, handleClose } = props;
|
||||
// hooks
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
@@ -29,28 +27,20 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: Partial<IState>) => {
|
||||
if (!state.id) return { status: "error" };
|
||||
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
try {
|
||||
const stateResponse = await updateStateCallback(state.id, formData);
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...stateResponse,
|
||||
state: "SUCCESS",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
await updateStateCallback(state.id, formData);
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state_group: state.group,
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
@@ -73,14 +63,15 @@ export const StateUpdate: FC<TStateUpdate> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "State could not be updated. Please try again.",
|
||||
});
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
element: "Project settings states page",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state_group: state.group,
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return { status: "error" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FC, useState, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronDown, Plus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
@@ -81,6 +81,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_GROUP_ADD_BUTTON}
|
||||
className={cn(
|
||||
"flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-primary-100/80 hover:text-custom-primary-100",
|
||||
(!isEditable || createState) && "cursor-not-allowed text-custom-text-400 hover:text-custom-text-400"
|
||||
|
||||
@@ -4,12 +4,12 @@ import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Loader, X } from "lucide-react";
|
||||
// plane imports
|
||||
import { EventProps, STATE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { STATE_TRACKER_EVENTS, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { AlertModalCore, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TStateDelete = {
|
||||
@@ -23,45 +23,39 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
||||
const { totalStates, state, deleteStateCallback, shouldTrackEvents } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { captureProjectStateEvent, setTrackElement } = useEventTracker();
|
||||
// states
|
||||
const [isDeleteModal, setIsDeleteModal] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState(false);
|
||||
// derived values
|
||||
const isDeleteDisabled = state.default ? true : totalStates === 1 ? true : false;
|
||||
|
||||
const captureEventIfEnabled = (props: EventProps) => {
|
||||
if (shouldTrackEvents) {
|
||||
captureProjectStateEvent(props);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteState = async () => {
|
||||
if (isDeleteDisabled) return;
|
||||
if (shouldTrackEvents) {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
|
||||
setIsDelete(true);
|
||||
|
||||
try {
|
||||
await deleteStateCallback(state.id);
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setIsDelete(false);
|
||||
} catch (error) {
|
||||
const errorStatus = error as unknown as { status: number; data: { error: string } };
|
||||
captureEventIfEnabled({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...state,
|
||||
state: "FAILED",
|
||||
},
|
||||
});
|
||||
if (shouldTrackEvents) {
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
id: state.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (errorStatus.status === 400) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -107,6 +101,7 @@ export const StateDelete: FC<TStateDelete> = observer((props) => {
|
||||
)}
|
||||
disabled={isDeleteDisabled}
|
||||
onClick={() => setIsDeleteModal(true)}
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_DELETE_BUTTON}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
|
||||
@@ -10,7 +10,8 @@ import type { IState } from "@plane/types";
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useProjectState } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
|
||||
type TStateDeleteModal = {
|
||||
isOpen: boolean;
|
||||
@@ -24,8 +25,6 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { captureProjectStateEvent } = useEventTracker();
|
||||
const { deleteState } = useProjectState();
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -40,11 +39,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||
|
||||
await deleteState(workspaceSlug.toString(), data.project_id, data.id)
|
||||
.then(() => {
|
||||
captureProjectStateEvent({
|
||||
captureSuccess({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "SUCCESS",
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
handleClose();
|
||||
@@ -63,11 +61,10 @@ export const StateDeleteModal: React.FC<TStateDeleteModal> = observer((props) =>
|
||||
title: "Error!",
|
||||
message: "State could not be deleted. Please try again.",
|
||||
});
|
||||
captureProjectStateEvent({
|
||||
captureError({
|
||||
eventName: STATE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "FAILED",
|
||||
id: data.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SetStateAction } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { GripVertical, Pencil } from "lucide-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { EIconSize, STATE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IState, TStateOperationsCallbacks } from "@plane/types";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// local imports
|
||||
@@ -70,6 +70,7 @@ export const StateItemTitle = observer((props: TStateItemTitleProps) => {
|
||||
<button
|
||||
className="flex-shrink-0 w-5 h-5 rounded flex justify-center items-center overflow-hidden transition-colors hover:bg-custom-background-80 cursor-pointer text-custom-text-200 hover:text-custom-text-100"
|
||||
onClick={() => setUpdateStateModal(true)}
|
||||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_LIST_EDIT_BUTTON}
|
||||
>
|
||||
<Pencil className="w-3 h-3" />
|
||||
</button>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserPermissions, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
// components
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ProjectCard } from "@/components/project";
|
||||
import { ProjectsLoader } from "@/components/ui";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useProjectFilter, useUserPermissions } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// assets
|
||||
import AllFiltersImage from "@/public/empty-state/project/all-filters.svg";
|
||||
@@ -26,7 +27,6 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
loader,
|
||||
fetchStatus,
|
||||
@@ -65,8 +65,8 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
||||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Project empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,8 @@ import type { IProject } from "@plane/types";
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type DeleteProjectModal = {
|
||||
@@ -29,7 +30,6 @@ const defaultValues = {
|
||||
export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
||||
const { isOpen, project, onClose } = props;
|
||||
// store hooks
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { deleteProject } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
@@ -62,9 +62,11 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
||||
if (projectId && projectId.toString() === project.id) router.push(`/${workspaceSlug}/projects`);
|
||||
|
||||
handleClose();
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.delete,
|
||||
payload: { ...project, state: "SUCCESS", element: "Project general settings" },
|
||||
payload: {
|
||||
id: project.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -73,9 +75,11 @@ export const DeleteProjectModal: React.FC<DeleteProjectModal> = (props) => {
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
captureProjectEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.delete,
|
||||
payload: { ...project, state: "FAILED", element: "Project general settings" },
|
||||
payload: {
|
||||
id: project.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Info, Lock } from "lucide-react";
|
||||
import { NETWORK_CHOICES, PROJECT_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NETWORK_CHOICES, PROJECT_TRACKER_ELEMENTS, PROJECT_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IProject, IWorkspace } from "@plane/types";
|
||||
@@ -27,7 +27,8 @@ import { TimezoneSelect } from "@/components/global";
|
||||
import { ProjectNetworkIcon } from "@/components/project";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
@@ -46,7 +47,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// store hooks
|
||||
const { captureProjectEvent } = useEventTracker();
|
||||
const { updateProject } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
@@ -58,7 +58,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
setValue,
|
||||
setError,
|
||||
reset,
|
||||
formState: { errors, dirtyFields },
|
||||
formState: { errors },
|
||||
getValues,
|
||||
} = useForm<IProject>({
|
||||
defaultValues: {
|
||||
@@ -92,15 +92,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
if (!workspaceSlug || !project) return;
|
||||
return updateProject(workspaceSlug.toString(), project.id, payload)
|
||||
.then((res) => {
|
||||
const changed_properties = Object.keys(dirtyFields);
|
||||
|
||||
captureProjectEvent({
|
||||
captureSuccess({
|
||||
eventName: PROJECT_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
changed_properties: changed_properties,
|
||||
state: "SUCCESS",
|
||||
element: "Project general settings",
|
||||
id: projectId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
@@ -110,9 +105,11 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureProjectEvent({
|
||||
captureError({
|
||||
eventName: PROJECT_TRACKER_EVENTS.update,
|
||||
payload: { ...payload, state: "FAILED", element: "Project general settings" },
|
||||
payload: {
|
||||
id: projectId,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -397,7 +394,13 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<>
|
||||
<Button variant="primary" type="submit" loading={isLoading} disabled={!isAdmin}>
|
||||
<Button
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.UPDATE_PROJECT_BUTTON}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
disabled={!isAdmin}
|
||||
>
|
||||
{isLoading ? `${t("updating")}...` : t("common.update_project")}
|
||||
</Button>
|
||||
<span className="text-sm italic text-custom-sidebar-text-400">
|
||||
|
||||
@@ -4,14 +4,15 @@ import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Briefcase } from "lucide-react";
|
||||
// i18n
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
// plane web constants
|
||||
// components
|
||||
import HeaderFilters from "./filters";
|
||||
@@ -22,7 +23,6 @@ export const ProjectsBaseHeader = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const pathname = usePathname();
|
||||
@@ -57,9 +57,9 @@ export const ProjectsBaseHeader = observer(() => {
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("Projects page");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.CREATE_HEADER_BUTTON}
|
||||
className="items-center gap-1"
|
||||
>
|
||||
<span className="hidden sm:inline-block">{t("workspace_projects.create.label")}</span>
|
||||
|
||||
@@ -14,7 +14,8 @@ import { IProject } from "@plane/types";
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type FormData = {
|
||||
@@ -39,7 +40,6 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { leaveProject } = useUserPermissions();
|
||||
|
||||
const {
|
||||
@@ -64,21 +64,26 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
||||
return leaveProject(workspaceSlug.toString(), project.id)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: project.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: project.id,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "FAILED",
|
||||
element: "Project settings members page",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
|
||||
@@ -7,7 +7,8 @@ import { TOAST_TYPE, Table, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ConfirmProjectMemberRemove } from "@/components/project";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web imports
|
||||
import { useProjectColumns } from "@/plane-web/components/projects/settings/useProjectColumns";
|
||||
@@ -30,7 +31,6 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
project: { removeMemberFromProject },
|
||||
} = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
// helper hooks
|
||||
const { columns, removeMemberModal, setRemoveMemberModal } = useProjectColumns({
|
||||
projectId,
|
||||
@@ -44,18 +44,27 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
||||
await leaveProject(workspaceSlug.toString(), projectId.toString())
|
||||
.then(async () => {
|
||||
router.push(`/${workspaceSlug}/projects`);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: projectId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err) =>
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.leave,
|
||||
payload: {
|
||||
project: projectId,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "You can’t leave this project yet.",
|
||||
message: err?.error || "Something went wrong. Please try again.",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} else
|
||||
await removeMemberFromProject(workspaceSlug.toString(), projectId.toString(), memberId).catch((err) =>
|
||||
setToast({
|
||||
|
||||
@@ -4,14 +4,14 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Search } from "lucide-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectMemberListItem, SendProjectInvitationModal } from "@/components/project";
|
||||
import { MembersSettingsLoader } from "@/components/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type TProjectMemberListProps = {
|
||||
projectId: string;
|
||||
@@ -23,8 +23,6 @@ export const ProjectMemberList: React.FC<TProjectMemberListProps> = observer((pr
|
||||
// states
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
// store hooks
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
project: { projectMemberIds, getProjectMemberDetails },
|
||||
} = useMember();
|
||||
@@ -73,9 +71,9 @@ export const ProjectMemberList: React.FC<TProjectMemberListProps> = observer((pr
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_MEMBERS_PAGE_HEADER");
|
||||
setInviteModal(true);
|
||||
}}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
|
||||
>
|
||||
{t("add_member")}
|
||||
</Button>
|
||||
|
||||
@@ -12,7 +12,8 @@ import { Avatar, Button, CustomSelect, CustomSearchSelect, TOAST_TYPE, setToast
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUserPermissions } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -45,7 +46,6 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
|
||||
const {
|
||||
project: { getProjectMemberDetails, bulkAddMembersToProject },
|
||||
@@ -86,22 +86,22 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Members added successfully.",
|
||||
});
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.add, {
|
||||
members: [
|
||||
...payload.members.map((member) => ({
|
||||
member_id: member.member_id,
|
||||
role: ROLE[member.role],
|
||||
})),
|
||||
],
|
||||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.add,
|
||||
payload: {
|
||||
members: [...payload.members.map((member) => member.member_id)],
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.project.add, {
|
||||
state: "FAILED",
|
||||
element: "Project settings members page",
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.project.add,
|
||||
payload: {
|
||||
members: [...payload.members.map((member) => member.member_id)],
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from "react";
|
||||
import { ChevronRight, ChevronUp } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IProject } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Loader } from "@plane/ui";
|
||||
@@ -43,7 +44,11 @@ export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) =>
|
||||
<div>
|
||||
{projectDetails ? (
|
||||
<div>
|
||||
<Button variant="danger" onClick={handleDelete}>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleDelete}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.DELETE_PROJECT_BUTTON}
|
||||
>
|
||||
Delete my project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { CircleMinus } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, EUserProjectRoles } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, EUserProjectRoles, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IUser, IWorkspaceMember, TProjectMembership } from "@plane/types";
|
||||
import { CustomMenu, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
@@ -70,6 +70,7 @@ export const NameColumn: React.FC<NameProps> = (props) => {
|
||||
<CustomMenu.MenuItem>
|
||||
<div
|
||||
className="flex items-center gap-x-1 cursor-pointer text-red-600 font-medium"
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.PROJECT_MEMBER_TABLE_CONTEXT_MENU}
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
>
|
||||
<CircleMinus className="flex-shrink-0 size-3.5" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SetStateAction, useEffect, useState } from "react";
|
||||
import { GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { Button } from "@plane/ui";
|
||||
import { LockedComponent } from "../icons/locked-component";
|
||||
|
||||
@@ -58,7 +59,13 @@ export const UpdateViewComponent = (props: Props) => {
|
||||
!areFiltersEqual &&
|
||||
isAuthorizedUser && (
|
||||
<>
|
||||
<Button variant="outline-primary" size="md" className="flex-shrink-0" onClick={() => setIsModalOpen(true)}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="md"
|
||||
className="flex-shrink-0"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.HEADER_SAVE_VIEW_BUTTON}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
Save as
|
||||
</Button>
|
||||
{isOwner && <>{updateButton}</>}
|
||||
|
||||
@@ -2,14 +2,20 @@ import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { CheckCheck, RefreshCw } from "lucide-react";
|
||||
// plane imports
|
||||
import { ENotificationLoader, ENotificationQueryParamType, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import {
|
||||
ENotificationLoader,
|
||||
ENotificationQueryParamType,
|
||||
NOTIFICATION_TRACKER_ELEMENTS,
|
||||
NOTIFICATION_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Spinner, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationFilter, NotificationHeaderMenuOption } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
type TNotificationSidebarHeaderOptions = {
|
||||
@@ -21,7 +27,6 @@ export const NotificationSidebarHeaderOptions: FC<TNotificationSidebarHeaderOpti
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { loader, getNotifications, markAllNotificationsAsRead } = useWorkspaceNotifications();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const refreshNotifications = async () => {
|
||||
@@ -49,8 +54,11 @@ export const NotificationSidebarHeaderOptions: FC<TNotificationSidebarHeaderOpti
|
||||
<Tooltip tooltipContent={t("notification.options.mark_all_as_read")} isMobile={isMobile} position="bottom">
|
||||
<div
|
||||
className="flex-shrink-0 w-5 h-5 flex justify-center items-center overflow-hidden cursor-pointer transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.MARK_ALL_AS_READ_BUTTON}
|
||||
onClick={() => {
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read);
|
||||
captureSuccess({
|
||||
eventName: NOTIFICATION_TRACKER_EVENTS.all_marked_read,
|
||||
});
|
||||
handleMarkAllNotificationsAsRead();
|
||||
}}
|
||||
>
|
||||
|
||||
+17
-7
@@ -3,14 +3,15 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ArchiveIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationItemOptionButton } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
// store
|
||||
import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
@@ -22,7 +23,6 @@ type TNotificationItemArchiveOption = {
|
||||
export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> = observer((props) => {
|
||||
const { workspaceSlug, notification } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
const { asJson: data, archiveNotification, unArchiveNotification } = notification;
|
||||
const { t } = useTranslation();
|
||||
@@ -31,10 +31,12 @@ export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> =
|
||||
try {
|
||||
const request = data.archived_at ? unArchiveNotification : archiveNotification;
|
||||
await request(workspaceSlug);
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.archive, {
|
||||
issue_id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
title: data.archived_at ? t("notification.toasts.unarchived") : t("notification.toasts.archived"),
|
||||
@@ -42,11 +44,19 @@ export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> =
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
captureError({
|
||||
eventName: data.archived_at ? NOTIFICATION_TRACKER_EVENTS.unarchive : NOTIFICATION_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationItemOptionButton
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.ARCHIVE_UNARCHIVE_BUTTON}
|
||||
tooltipContent={
|
||||
data.archived_at ? t("notification.options.mark_unarchive") : t("notification.options.mark_archive")
|
||||
}
|
||||
|
||||
+17
-7
@@ -3,14 +3,15 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MessageSquare } from "lucide-react";
|
||||
import { NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { NOTIFICATION_TRACKER_ELEMENTS, NOTIFICATION_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationItemOptionButton } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspaceNotifications } from "@/hooks/store";
|
||||
// store
|
||||
import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
@@ -22,7 +23,6 @@ type TNotificationItemReadOption = {
|
||||
export const NotificationItemReadOption: FC<TNotificationItemReadOption> = observer((props) => {
|
||||
const { workspaceSlug, notification } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
const { asJson: data, markNotificationAsRead, markNotificationAsUnRead } = notification;
|
||||
const { t } = useTranslation();
|
||||
@@ -31,10 +31,12 @@ export const NotificationItemReadOption: FC<TNotificationItemReadOption> = obser
|
||||
try {
|
||||
const request = data.read_at ? markNotificationAsUnRead : markNotificationAsRead;
|
||||
await request(workspaceSlug);
|
||||
captureEvent(NOTIFICATION_TRACKER_EVENTS.all_marked_read, {
|
||||
issue_id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
title: data.read_at ? t("notification.toasts.unread") : t("notification.toasts.read"),
|
||||
@@ -42,11 +44,19 @@ export const NotificationItemReadOption: FC<TNotificationItemReadOption> = obser
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
captureError({
|
||||
eventName: data.read_at ? NOTIFICATION_TRACKER_EVENTS.mark_unread : NOTIFICATION_TRACKER_EVENTS.mark_read,
|
||||
payload: {
|
||||
id: data?.data?.issue?.id,
|
||||
tab: currentNotificationTab,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationItemOptionButton
|
||||
data-ph-element={NOTIFICATION_TRACKER_ELEMENTS.MARK_READ_UNREAD_BUTTON}
|
||||
tooltipContent={data.read_at ? t("notification.options.mark_unread") : t("notification.options.mark_read")}
|
||||
callBack={handleNotificationUpdate}
|
||||
>
|
||||
|
||||
@@ -11,7 +11,8 @@ import { IWorkspace } from "@plane/types";
|
||||
// ui
|
||||
import { Button, CustomSelect, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
@@ -51,7 +52,6 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { createWorkspace } = useWorkspace();
|
||||
// form info
|
||||
const {
|
||||
@@ -71,13 +71,9 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
|
||||
await createWorkspace(formData)
|
||||
.then(async (res) => {
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
element: "Create workspace page",
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -88,12 +84,10 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
if (onSubmit) await onSubmit(res);
|
||||
})
|
||||
.catch(() => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.create,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
element: "Create workspace page",
|
||||
},
|
||||
payload: { slug: formData.slug },
|
||||
error: new Error("Error creating workspace"),
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -248,7 +242,14 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{secondaryButton}
|
||||
<Button variant="primary" type="submit" size="md" disabled={!isValid} loading={isSubmitting}>
|
||||
<Button
|
||||
data-ph-element="CREATE_WORKSPACE_BUTTON"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="md"
|
||||
disabled={!isValid}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? t(primaryButtonText.loading) : t(primaryButtonText.default)}
|
||||
</Button>
|
||||
{!secondaryButton && (
|
||||
|
||||
@@ -13,7 +13,8 @@ import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { cn } from "@plane/utils";
|
||||
import { useEventTracker, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
|
||||
type Props = {
|
||||
@@ -31,7 +32,6 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { deleteWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
||||
@@ -64,13 +64,9 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
||||
await fetchCurrentUserSettings();
|
||||
handleClose();
|
||||
router.push(getWorkspaceRedirectionUrl());
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: data.slug },
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
@@ -84,13 +80,10 @@ export const DeleteWorkspaceForm: React.FC<Props> = observer((props) => {
|
||||
title: t("workspace_settings.settings.general.delete_modal.error_title"),
|
||||
message: t("workspace_settings.settings.general.delete_modal.error_message"),
|
||||
});
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
...data,
|
||||
state: "FAILED",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: data.slug },
|
||||
error: new Error("Error deleting workspace"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,13 +5,14 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronDown, LinkIcon, Trash2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { CustomSelect, TOAST_TYPE, setToast, TContextMenuItem, CustomMenu } from "@plane/ui";
|
||||
import { cn, copyTextToClipboard } from "@plane/utils";
|
||||
// components
|
||||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
@@ -93,7 +94,12 @@ export const WorkspaceInvitationsListItem: FC<Props> = observer((props) => {
|
||||
},
|
||||
{
|
||||
key: "remove",
|
||||
action: () => setRemoveMemberModal(true),
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: MEMBER_TRACKER_ELEMENTS.WORKSPACE_INVITATIONS_LIST_CONTEXT_MENU,
|
||||
});
|
||||
setRemoveMemberModal(true);
|
||||
},
|
||||
title: t("common.remove"),
|
||||
icon: Trash2,
|
||||
shouldRender: isAdmin,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { ROLE, EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IUser, IWorkspaceMember } from "@plane/types";
|
||||
// plane ui
|
||||
import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
@@ -74,6 +74,7 @@ export const NameColumn: React.FC<NameProps> = (props) => {
|
||||
<div
|
||||
className="flex items-center gap-x-3 cursor-pointer"
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.WORKSPACE_MEMBER_TABLE_CONTEXT_MENU}
|
||||
>
|
||||
<Trash2 className="size-3.5 align-middle" /> {id === currentUser?.id ? "Leave " : "Remove "}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,8 @@ import { MembersLayoutLoader } from "@/components/ui/loader/layouts/members-layo
|
||||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace";
|
||||
// constants
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useMember, useUser, useUserPermissions, useUserSettings, useWorkspace } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useMemberColumns } from "@/plane-web/components/workspace/settings/useMemberColumns";
|
||||
|
||||
@@ -32,7 +33,6 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||
workspace: { removeMemberFromWorkspace },
|
||||
} = useMember();
|
||||
const { leaveWorkspace } = useUserPermissions();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { getWorkspaceRedirectionUrl } = useWorkspace();
|
||||
const { fetchCurrentUserSettings } = useUserSettings();
|
||||
const { t } = useTranslation();
|
||||
@@ -45,18 +45,27 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||
.then(async () => {
|
||||
await fetchCurrentUserSettings();
|
||||
router.push(getWorkspaceRedirectionUrl());
|
||||
captureEvent(MEMBER_TRACKER_EVENTS.workspace.leave, {
|
||||
state: "SUCCESS",
|
||||
element: "Workspace settings members page",
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
|
||||
payload: {
|
||||
workspace: workspaceSlug,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((err: any) =>
|
||||
.catch((err: any) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
|
||||
payload: {
|
||||
workspace: workspaceSlug,
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: err?.error || t("something_went_wrong_please_try_again"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (memberId: string) => {
|
||||
|
||||
@@ -15,7 +15,8 @@ import { LogoSpinner } from "@/components/common";
|
||||
import { WorkspaceImageUploadModal } from "@/components/core";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useEventTracker, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { DeleteWorkspaceSection } from "@/plane-web/components/workspace";
|
||||
|
||||
@@ -31,7 +32,6 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||
// store hooks
|
||||
const { captureWorkspaceEvent } = useEventTracker();
|
||||
const { currentWorkspace, updateWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
@@ -61,13 +61,9 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
|
||||
await updateWorkspace(currentWorkspace.slug, payload)
|
||||
.then((res) => {
|
||||
captureWorkspaceEvent({
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
});
|
||||
setToast({
|
||||
title: "Success!",
|
||||
@@ -76,12 +72,10 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureWorkspaceEvent({
|
||||
captureError({
|
||||
eventName: WORKSPACE_TRACKER_EVENTS.update,
|
||||
payload: {
|
||||
state: "FAILED",
|
||||
element: "Workspace general settings page",
|
||||
},
|
||||
payload: { slug: currentWorkspace.slug },
|
||||
error: err,
|
||||
});
|
||||
console.error(err);
|
||||
});
|
||||
@@ -282,7 +276,12 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
|
||||
{isAdmin && (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isLoading}>
|
||||
<Button
|
||||
data-ph-element="WORKSPACE_SETTINGS_UPDATE_WORKSPACE_BUTTON"
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? t("updating") : t("workspace_settings.settings.general.update_workspace")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createRoot } from "react-dom/client";
|
||||
import { LinkIcon, Settings, Share2, LogOut, MoreHorizontal, ChevronRight } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// plane helpers
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
@@ -23,7 +23,7 @@ import { Logo } from "@/components/common/logo";
|
||||
import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane-web components
|
||||
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
|
||||
@@ -59,7 +59,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||
// store hooks
|
||||
const { sidebarCollapsed } = useAppTheme();
|
||||
const { t } = useTranslation();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { getPartialProjectById } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
@@ -97,7 +96,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||
);
|
||||
|
||||
const handleLeaveProject = () => {
|
||||
setTrackElement("APP_SIDEBAR_PROJECT_DROPDOWN");
|
||||
setLeaveProjectModal(true);
|
||||
};
|
||||
|
||||
@@ -376,7 +374,10 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||
</CustomMenu.MenuItem>
|
||||
{/* leave project */}
|
||||
{!isAuthorized && (
|
||||
<CustomMenu.MenuItem onClick={handleLeaveProject}>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={handleLeaveProject}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.SIDEBAR_PROJECT_QUICK_ACTIONS}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<LogOut className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||
<span>{t("leave_project")}</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { Briefcase, ChevronRight, Plus } from "lucide-react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Loader, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
@@ -17,7 +17,7 @@ import { CreateProjectModal } from "@/components/project";
|
||||
import { SidebarProjectsListItem } from "@/components/workspace";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppTheme, useCommandPalette, useProject, useUserPermissions } from "@/hooks/store";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types";
|
||||
|
||||
@@ -33,7 +33,6 @@ export const SidebarProjectsList: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { sidebarCollapsed } = useAppTheme();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { loader, getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject();
|
||||
@@ -193,9 +192,9 @@ export const SidebarProjectsList: FC = observer(() => {
|
||||
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_TOOLTIP}
|
||||
className="p-0.5 rounded hover:bg-custom-sidebar-background-80 flex-shrink-0"
|
||||
onClick={() => {
|
||||
setTrackElement(`APP_SIDEBAR_JOINED_BLOCK`);
|
||||
setIsProjectModalOpen(true);
|
||||
}}
|
||||
aria-label={t("aria_labels.projects_sidebar.create_new_project")}
|
||||
@@ -277,8 +276,8 @@ export const SidebarProjectsList: FC = observer(() => {
|
||||
"p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_BUTTON}
|
||||
onClick={() => {
|
||||
setTrackElement("Sidebar");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel, EUserWorkspaceRoles, SIDEBAR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EUserPermissionsLevel, EUserWorkspaceRoles, SIDEBAR_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { usePlatformOS } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
@@ -11,7 +11,8 @@ import { Tooltip } from "@plane/ui";
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
import { NotificationAppSidebarOption } from "@/components/workspace-notifications";
|
||||
// hooks
|
||||
import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useAppTheme, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
export interface SidebarUserMenuItemProps {
|
||||
item: {
|
||||
@@ -33,7 +34,6 @@ export const SidebarUserMenuItem: FC<SidebarUserMenuItemProps> = observer((props
|
||||
// package hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||
const { isMobile } = usePlatformOS();
|
||||
@@ -49,8 +49,11 @@ export const SidebarUserMenuItem: FC<SidebarUserMenuItemProps> = observer((props
|
||||
if (window.innerWidth < 768) {
|
||||
toggleSidebar();
|
||||
}
|
||||
captureEvent(SIDEBAR_TRACKER_EVENTS.click, {
|
||||
destination: itemKey,
|
||||
captureClick({
|
||||
elementName: SIDEBAR_TRACKER_ELEMENTS.USER_MENU_ITEM,
|
||||
context: {
|
||||
destination: itemKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { GLOBAL_VIEW_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { GLOBAL_VIEW_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IWorkspaceView } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// hooks
|
||||
import { useGlobalView, useEventTracker } from "@/hooks/store";
|
||||
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
data: IWorkspaceView;
|
||||
@@ -26,7 +27,6 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { deleteGlobalView } = useGlobalView();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const handleClose = () => onClose();
|
||||
|
||||
@@ -37,15 +37,20 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
|
||||
|
||||
await deleteGlobalView(workspaceSlug.toString(), data.id)
|
||||
.then(() => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, {
|
||||
view_id: data.id,
|
||||
state: "SUCCESS",
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
view_id: data.id,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error: any) => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.delete, {
|
||||
view_id: data.id,
|
||||
state: "FAILED",
|
||||
captureError({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.delete,
|
||||
payload: {
|
||||
view_id: data.id,
|
||||
},
|
||||
error: error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
GLOBAL_VIEW_TOUR_TRACKER_EVENTS,
|
||||
GLOBAL_VIEW_TRACKER_ELEMENTS,
|
||||
GLOBAL_VIEW_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { TStaticViewTypes } from "@plane/types";
|
||||
// components
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
} from "@/components/workspace";
|
||||
// constants
|
||||
// store hooks
|
||||
import { captureSuccess } from "@/helpers/event-tracker.helper";
|
||||
import { useEventTracker, useGlobalView, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
const ViewTab = observer((props: { viewId: string }) => {
|
||||
@@ -77,11 +79,14 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
||||
// bring the active view to the centre of the header
|
||||
useEffect(() => {
|
||||
if (globalViewId && currentWorkspaceViews) {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.open, {
|
||||
view_id: globalViewId,
|
||||
view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||
? "Default"
|
||||
: "Custom",
|
||||
captureSuccess({
|
||||
eventName: GLOBAL_VIEW_TRACKER_EVENTS.open,
|
||||
payload: {
|
||||
view_id: globalViewId,
|
||||
view_type: ["all-issues", "assigned", "created", "subscribed"].includes(globalViewId.toString())
|
||||
? "Default"
|
||||
: "Custom",
|
||||
},
|
||||
});
|
||||
const activeTabElement = document.querySelector(`#global-view-${globalViewId.toString()}`);
|
||||
if (activeTabElement && containerRef.current) {
|
||||
@@ -115,6 +120,7 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
||||
{isAuthorizedUser ? (
|
||||
<button
|
||||
type="button"
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
className="sticky -right-4 flex flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 py-3 hover:border-custom-border-200 hover:text-custom-text-400"
|
||||
onClick={() => setCreateViewModal(true)}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { GLOBAL_VIEW_TOUR_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { GLOBAL_VIEW_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { IWorkspaceView } from "@plane/types";
|
||||
// ui
|
||||
import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
@@ -47,7 +47,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
||||
|
||||
await createGlobalView(workspaceSlug.toString(), payloadData)
|
||||
.then((res) => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.create, {
|
||||
captureEvent(GLOBAL_VIEW_TRACKER_EVENTS.create, {
|
||||
view_id: res.id,
|
||||
applied_filters: res.filters,
|
||||
state: "SUCCESS",
|
||||
@@ -62,7 +62,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
||||
handleClose();
|
||||
})
|
||||
.catch(() => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.create, {
|
||||
captureEvent(GLOBAL_VIEW_TRACKER_EVENTS.create, {
|
||||
applied_filters: payload?.filters,
|
||||
state: "FAILED",
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
||||
await updateGlobalView(workspaceSlug.toString(), data.id, payloadData)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, {
|
||||
captureEvent(GLOBAL_VIEW_TRACKER_EVENTS.update, {
|
||||
view_id: res.id,
|
||||
applied_filters: res.filters,
|
||||
state: "SUCCESS",
|
||||
@@ -101,7 +101,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
captureEvent(GLOBAL_VIEW_TOUR_TRACKER_EVENTS.update, {
|
||||
captureEvent(GLOBAL_VIEW_TRACKER_EVENTS.update, {
|
||||
view_id: data.id,
|
||||
applied_filters: data.filters,
|
||||
state: "FAILED",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IWorkspaceView } from "@plane/types";
|
||||
import { CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
@@ -14,6 +14,7 @@ import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "@/compone
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useUser, useUserPermissions } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
@@ -93,6 +94,9 @@ export const WorkspaceViewQuickActions: React.FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: GLOBAL_VIEW_TRACKER_ELEMENTS.QUICK_ACTIONS,
|
||||
});
|
||||
item.action();
|
||||
}}
|
||||
className={cn(
|
||||
|
||||
@@ -7,13 +7,15 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
// ui
|
||||
import { GLOBAL_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { calculateTotalFilters, truncateText } from "@plane/utils";
|
||||
// components
|
||||
import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "@/components/workspace";
|
||||
// helpers
|
||||
// store hooks
|
||||
import { useEventTracker, useGlobalView } from "@/hooks/store";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useGlobalView } from "@/hooks/store";
|
||||
|
||||
type Props = { viewId: string };
|
||||
|
||||
@@ -26,7 +28,6 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { getViewDetailsById } = useGlobalView();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
// derived data
|
||||
const view = getViewDetailsById(viewId);
|
||||
|
||||
@@ -58,7 +59,9 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setTrackElement("List view");
|
||||
captureClick({
|
||||
elementName: GLOBAL_VIEW_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
});
|
||||
setUpdateViewModal(true);
|
||||
}}
|
||||
>
|
||||
@@ -71,6 +74,9 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
captureClick({
|
||||
elementName: GLOBAL_VIEW_TRACKER_ELEMENTS.LIST_ITEM,
|
||||
});
|
||||
setDeleteViewModal(true);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useMemo } from "react";
|
||||
// plane imports
|
||||
import { IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
// constants
|
||||
import { IS_FAVORITE_MENU_OPEN, PROJECT_PAGE_TRACKER_EVENTS } from "@plane/constants";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
import { EPageAccess } from "@plane/types/src/enums";
|
||||
// ui
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
import { copyUrlToClipboard } from "@plane/utils";
|
||||
// helpers
|
||||
import { captureSuccess, captureError } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import { useCollaborativePageActions } from "@/hooks/use-collaborative-page-actions";
|
||||
// store types
|
||||
@@ -68,12 +71,23 @@ export const usePageOperations = (
|
||||
duplicate: async () => {
|
||||
try {
|
||||
await duplicate();
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.duplicate,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page duplicated successfully.",
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.duplicate,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -85,17 +99,33 @@ export const usePageOperations = (
|
||||
openInNewTab: () => window.open(pageLink, "_blank"),
|
||||
toggleAccess: async () => {
|
||||
const changedPageType = access === EPageAccess.PUBLIC ? "private" : "public";
|
||||
const eventName = PROJECT_PAGE_TRACKER_EVENTS.access_update;
|
||||
|
||||
try {
|
||||
if (access === EPageAccess.PUBLIC)
|
||||
await executeCollaborativeAction({ type: "sendMessageToServer", message: "make-private" });
|
||||
else await executeCollaborativeAction({ type: "sendMessageToServer", message: "make-public" });
|
||||
|
||||
captureSuccess({
|
||||
eventName,
|
||||
payload: {
|
||||
id: page.id,
|
||||
from_access: access === EPageAccess.PUBLIC ? "Public" : "Private",
|
||||
to_access: changedPageType === EPageAccess.PUBLIC ? "Public" : "Private",
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: `The page has been marked ${changedPageType} and moved to the ${changedPageType} section.`,
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -107,12 +137,23 @@ export const usePageOperations = (
|
||||
if (archived_at) {
|
||||
try {
|
||||
await executeCollaborativeAction({ type: "sendMessageToServer", message: "unarchive" });
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.restore,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page restored successfully.",
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.restore,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -122,12 +163,23 @@ export const usePageOperations = (
|
||||
} else {
|
||||
try {
|
||||
await executeCollaborativeAction({ type: "sendMessageToServer", message: "archive" });
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.archive,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page archived successfully.",
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.archive,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -138,34 +190,73 @@ export const usePageOperations = (
|
||||
},
|
||||
toggleFavorite: () => {
|
||||
if (is_favorite) {
|
||||
removePageFromFavorites().then(() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page removed from favorites.",
|
||||
removePageFromFavorites()
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.unfavorite,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page removed from favorites.",
|
||||
});
|
||||
})
|
||||
);
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.unfavorite,
|
||||
error,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
addToFavorites().then(() => {
|
||||
if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page added to favorites.",
|
||||
addToFavorites()
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.favorite,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
if (!isFavoriteMenuOpen) toggleFavoriteMenu(true);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page added to favorites.",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.favorite,
|
||||
error,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleLock: async () => {
|
||||
if (is_locked) {
|
||||
try {
|
||||
await executeCollaborativeAction({ type: "sendMessageToServer", message: "unlock" });
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.unlock,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page unlocked successfully.",
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.unlock,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -175,12 +266,23 @@ export const usePageOperations = (
|
||||
} else {
|
||||
try {
|
||||
await executeCollaborativeAction({ type: "sendMessageToServer", message: "lock" });
|
||||
captureSuccess({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.lock,
|
||||
payload: {
|
||||
id: page.id,
|
||||
state: "SUCCESS",
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Page locked successfully.",
|
||||
});
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: PROJECT_PAGE_TRACKER_EVENTS.lock,
|
||||
error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -200,6 +302,7 @@ export const usePageOperations = (
|
||||
is_favorite,
|
||||
is_locked,
|
||||
isFavoriteMenuOpen,
|
||||
page.id,
|
||||
removePageFromFavorites,
|
||||
toggleFavoriteMenu,
|
||||
]);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FC, ReactNode, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { EProjectNetwork } from "@plane/types/src/enums";
|
||||
@@ -12,6 +12,7 @@ import { JoinProject } from "@/components/auth-screens";
|
||||
import { LogoSpinner } from "@/components/common";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
import { ETimeLineTypeType } from "@/components/gantt-chart/contexts";
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
// hooks
|
||||
import {
|
||||
useCommandPalette,
|
||||
@@ -195,8 +196,8 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
||||
title={t("workspace_projects.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_projects.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Project empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON });
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { GROUP_WORKSPACE_TRACKER_EVENT } from "@plane/constants";
|
||||
// helpers
|
||||
import { getUserRole } from "@plane/utils";
|
||||
// hooks
|
||||
import { captureClick } from "@/helpers/event-tracker.helper";
|
||||
import { useWorkspace, useUser, useInstance, useUserPermissions } from "@/hooks/store";
|
||||
// dynamic imports
|
||||
const PostHogPageView = dynamic(() => import("@/lib/posthog-view"), { ssr: false });
|
||||
@@ -33,6 +34,8 @@ const PostHogProvider: FC<IPosthogWrapper> = observer((props) => {
|
||||
);
|
||||
const currentWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug?.toString());
|
||||
const is_telemetry_enabled = instance?.is_telemetry_enabled || false;
|
||||
const is_posthog_enabled =
|
||||
process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST && is_telemetry_enabled;
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
@@ -65,7 +68,29 @@ const PostHogProvider: FC<IPosthogWrapper> = observer((props) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST && is_telemetry_enabled)
|
||||
useEffect(() => {
|
||||
const clickHandler = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
// Use closest to find the nearest parent element with data-ph-element attribute
|
||||
const elementWithAttribute = target.closest("[data-ph-element]") as HTMLElement;
|
||||
if (elementWithAttribute) {
|
||||
const element = elementWithAttribute.getAttribute("data-ph-element");
|
||||
if (element) {
|
||||
captureClick({ elementName: element });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (is_posthog_enabled) {
|
||||
document.addEventListener("click", clickHandler);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", clickHandler);
|
||||
};
|
||||
}, [is_posthog_enabled]);
|
||||
|
||||
if (is_posthog_enabled)
|
||||
return (
|
||||
<PHProvider client={posthog}>
|
||||
<PostHogPageView />
|
||||
|
||||
@@ -5,14 +5,9 @@ import {
|
||||
GROUP_WORKSPACE_TRACKER_EVENT,
|
||||
EventProps,
|
||||
IssueEventProps,
|
||||
getCycleEventPayload,
|
||||
getIssueEventPayload,
|
||||
getModuleEventPayload,
|
||||
getProjectEventPayload,
|
||||
getProjectStateEventPayload,
|
||||
getWorkspaceEventPayload,
|
||||
getPageEventPayload,
|
||||
WORKSPACE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { CoreRootStore } from "./root.store";
|
||||
|
||||
@@ -26,10 +21,6 @@ export interface ICoreEventTrackerStore {
|
||||
setTrackElement: (element: string) => void;
|
||||
captureEvent: (eventName: string, payload?: any) => void;
|
||||
joinWorkspaceMetricGroup: (workspaceId?: string) => void;
|
||||
captureWorkspaceEvent: (props: EventProps) => void;
|
||||
captureProjectEvent: (props: EventProps) => void;
|
||||
captureCycleEvent: (props: EventProps) => void;
|
||||
captureModuleEvent: (props: EventProps) => void;
|
||||
capturePageEvent: (props: EventProps) => void;
|
||||
captureIssueEvent: (props: IssueEventProps) => void;
|
||||
captureProjectStateEvent: (props: EventProps) => void;
|
||||
@@ -48,8 +39,6 @@ export abstract class CoreEventTrackerStore implements ICoreEventTrackerStore {
|
||||
resetSession: action,
|
||||
setTrackElement: action,
|
||||
captureEvent: action,
|
||||
captureProjectEvent: action,
|
||||
captureCycleEvent: action,
|
||||
});
|
||||
// store
|
||||
this.rootStore = _rootStore;
|
||||
@@ -109,68 +98,6 @@ export abstract class CoreEventTrackerStore implements ICoreEventTrackerStore {
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the workspace crud related events.
|
||||
* @param {EventProps} props
|
||||
*/
|
||||
captureWorkspaceEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
if (eventName === WORKSPACE_TRACKER_EVENTS.create && 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.
|
||||
* @param {EventProps} props
|
||||
*/
|
||||
captureProjectEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
const eventPayload: any = getProjectEventPayload({
|
||||
...this.getRequiredProperties,
|
||||
...payload,
|
||||
element: payload.element ?? this.trackElement,
|
||||
});
|
||||
posthog?.capture(eventName, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the cycle related events.
|
||||
* @param {EventProps} props
|
||||
*/
|
||||
captureCycleEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
const eventPayload: any = getCycleEventPayload({
|
||||
...this.getRequiredProperties,
|
||||
...payload,
|
||||
element: payload.element ?? this.trackElement,
|
||||
});
|
||||
posthog?.capture(eventName, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the module related events.
|
||||
* @param {EventProps} props
|
||||
*/
|
||||
captureModuleEvent = (props: EventProps) => {
|
||||
const { eventName, payload } = props;
|
||||
const eventPayload: any = getModuleEventPayload({
|
||||
...this.getRequiredProperties,
|
||||
...payload,
|
||||
element: payload.element ?? this.trackElement,
|
||||
});
|
||||
posthog?.capture(eventName, eventPayload);
|
||||
this.setTrackElement(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description: Captures the project pages related events.
|
||||
* @param {EventProps} props
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import posthog from "posthog-js";
|
||||
|
||||
export type TEventState = "SUCCESS" | "ERROR";
|
||||
export type TElementContext = Record<string, any>;
|
||||
export type TEventContext = Record<string, any>;
|
||||
export type TInteractionType = "clicked" | "viewed" | "hovered";
|
||||
|
||||
type TCaptureElementParams = {
|
||||
elementName: string;
|
||||
interaction_type: TInteractionType;
|
||||
context?: TElementContext;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture UI element interactions (clicks, hovers, views, etc.)
|
||||
* This helps understand user behavior and interaction patterns
|
||||
*
|
||||
* @param element - Generic UI element type
|
||||
* @param context - Context about where and why the interaction happened
|
||||
*/
|
||||
const captureElement = (params: TCaptureElementParams) => {
|
||||
const { elementName, interaction_type, context } = params;
|
||||
if (!posthog) return;
|
||||
|
||||
const elementEvent = `${elementName}_${interaction_type}`;
|
||||
|
||||
const payload = {
|
||||
element_type: elementName,
|
||||
timestamp: new Date().toISOString(),
|
||||
...context,
|
||||
};
|
||||
|
||||
posthog.capture(elementEvent, payload);
|
||||
};
|
||||
|
||||
type TCaptureClickParams = Omit<TCaptureElementParams, "interaction_type">;
|
||||
/**
|
||||
* Capture click events
|
||||
* @param element - The element that was clicked
|
||||
* @param context - Additional context
|
||||
*/
|
||||
export const captureClick = (params: TCaptureClickParams) => {
|
||||
captureElement({ ...params, interaction_type: "clicked" });
|
||||
};
|
||||
|
||||
type TCaptureViewParams = Omit<TCaptureElementParams, "interaction_type">;
|
||||
/**
|
||||
* Capture view events
|
||||
* @param element - The element that was viewed
|
||||
* @param context - Additional context
|
||||
*/
|
||||
export const captureView = (params: TCaptureViewParams) => {
|
||||
captureElement({ ...params, interaction_type: "viewed" });
|
||||
};
|
||||
|
||||
type TCaptureHoverParams = Omit<TCaptureElementParams, "interaction_type">;
|
||||
/**
|
||||
* Capture hover events
|
||||
* @param element - The element that was hovered
|
||||
* @param context - Additional context
|
||||
*/
|
||||
export const captureHover = (params: TCaptureHoverParams) => {
|
||||
captureElement({ ...params, interaction_type: "hovered" });
|
||||
};
|
||||
|
||||
type TCaptureEventParams = {
|
||||
eventName: string;
|
||||
payload?: Record<string, any>;
|
||||
context?: TEventContext;
|
||||
state: TEventState;
|
||||
};
|
||||
/**
|
||||
* Capture business events (outcomes, state changes, etc.)
|
||||
* This helps understand business metrics and conversion rates
|
||||
*
|
||||
* @param eventName - Business event name (e.g., "cycle_created", "project_updated")
|
||||
* @param state - Success or error state
|
||||
* @param payload - Event-specific data
|
||||
* @param context - Additional context
|
||||
*/
|
||||
const captureEvent = (params: TCaptureEventParams) => {
|
||||
const { eventName, payload, context, state } = params;
|
||||
if (!posthog) return;
|
||||
|
||||
const finalPayload = {
|
||||
...context,
|
||||
...payload,
|
||||
state,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
posthog.capture(eventName, finalPayload);
|
||||
};
|
||||
|
||||
type TCaptureSuccessParams = Omit<TCaptureEventParams, "state">;
|
||||
/**
|
||||
* Capture success events
|
||||
* @param eventName - The name of the event
|
||||
* @param payload - Additional payload
|
||||
* @param context - Additional context
|
||||
*/
|
||||
export const captureSuccess = (params: TCaptureSuccessParams) => {
|
||||
captureEvent({ ...params, state: "SUCCESS" });
|
||||
};
|
||||
|
||||
type TCaptureErrorParams = Omit<TCaptureEventParams, "state"> & {
|
||||
error?: Error | string;
|
||||
};
|
||||
/**
|
||||
* Capture error events
|
||||
* @param eventName - The name of the event
|
||||
* @param error - The error object
|
||||
* @param payload - Additional payload
|
||||
* @param context - Additional context
|
||||
*/
|
||||
export const captureError = (params: TCaptureErrorParams) => {
|
||||
captureEvent({ ...params, state: "ERROR", payload: { ...params.payload, error: params.error } });
|
||||
};
|
||||
|
||||
type TCaptureElementAndEventParams = {
|
||||
element: Omit<TCaptureElementParams, "interaction_type">;
|
||||
event: TCaptureEventParams;
|
||||
};
|
||||
/**
|
||||
* Capture both element interaction and business event together
|
||||
* @param element - The element that was interacted with
|
||||
* @param event - The business event that was triggered
|
||||
*/
|
||||
export const captureElementAndEvent = (params: TCaptureElementAndEventParams) => {
|
||||
const { element, event } = params;
|
||||
// Capture the element interaction first
|
||||
captureElement({ ...element, interaction_type: "clicked" });
|
||||
|
||||
// Then capture the business event
|
||||
captureEvent(event);
|
||||
};
|
||||
Reference in New Issue
Block a user