From d0f1bb24fddc7f961b75252fea5aaea84b2814be Mon Sep 17 00:00:00 2001 From: liuyi Date: Tue, 6 Aug 2024 17:15:15 +0800 Subject: [PATCH] chore(core): replace with new track impl (#7735) --- .../core/src/commands/affine-navigation.tsx | 7 +- .../affine/ai-onboarding/edgeless.dialog.tsx | 8 +- .../affine/ai-onboarding/general.dialog.tsx | 8 +- .../core/src/components/affine/auth/oauth.tsx | 4 +- .../src/components/affine/auth/sign-in.tsx | 10 +- .../affine/auth/user-plan-button.tsx | 6 +- .../affine/create-workspace-modal/index.tsx | 6 +- .../page-history-modal/history-modal.tsx | 37 +- .../quota-reached-modal/cloud-quota-modal.tsx | 8 +- .../account-setting/ai-usage-panel.tsx | 12 +- .../setting-modal/account-setting/index.tsx | 26 +- .../general-setting/billing/index.tsx | 45 +-- .../general-setting/plans/actions.tsx | 29 +- .../plans/ai/actions/cancel.tsx | 24 +- .../plans/ai/actions/resume.tsx | 26 +- .../plans/ai/actions/subscribe.tsx | 8 +- .../general-setting/plans/ai/ai-plan.tsx | 10 +- .../general-setting/plans/plan-card.tsx | 44 +-- .../setting-modal/setting-sidebar/index.tsx | 57 ++-- .../new-workspace-setting-detail/members.tsx | 9 +- .../share-menu/share-page.tsx | 9 +- .../app-sidebar/app-download-button/index.tsx | 6 +- .../block-suite-editor/ai/setup-provider.tsx | 7 +- .../block-suite-editor/ai/tracker.ts | 16 +- .../specs/custom/linked-widget.ts | 9 +- .../specs/custom/spec-patchers.tsx | 27 +- .../block-suite-header/menu/index.tsx | 31 +- .../block-suite-mode-switch/index.tsx | 10 +- .../page-list/components/new-page-button.tsx | 20 +- .../page-list/docs/page-list-header.tsx | 19 +- .../components/page-list/operation-cell.tsx | 11 +- .../user-with-workspace-list/index.tsx | 15 +- .../root-app-sidebar/import-page.tsx | 23 +- .../src/components/root-app-sidebar/index.tsx | 22 +- .../components/root-app-sidebar/user-info.tsx | 9 +- .../components/workspace-selector/index.tsx | 6 +- .../components/workspace-upgrade/upgrade.tsx | 6 +- .../core/src/hooks/affine/use-export-page.ts | 6 +- ...se-register-blocksuite-editor-commands.tsx | 8 +- .../core/src/hooks/affine/use-share-url.ts | 7 +- .../hooks/affine/use-subscription-notify.tsx | 10 +- .../core/src/hooks/use-app-updater.ts | 30 +- packages/frontend/core/src/mixpanel/auto.ts | 10 +- packages/frontend/core/src/mixpanel/events.ts | 320 ++++++++++++++++-- packages/frontend/core/src/mixpanel/types.ts | 22 +- .../explorer/views/nodes/collection/index.tsx | 24 +- .../views/nodes/collection/operations.tsx | 38 +-- .../explorer/views/nodes/doc/index.tsx | 20 +- .../explorer/views/nodes/doc/operations.tsx | 38 +-- .../explorer/views/nodes/folder/index.tsx | 141 ++------ .../explorer/views/nodes/tag/index.tsx | 14 +- .../explorer/views/nodes/tag/operations.tsx | 37 +- .../views/sections/collections/index.tsx | 8 +- .../views/sections/favorites/index.tsx | 36 +- .../sections/migration-favorites/index.tsx | 18 +- .../views/sections/organize/index.tsx | 16 +- .../explorer/views/sections/tags/index.tsx | 8 +- .../frontend/core/src/pages/subscribe.tsx | 8 +- .../workspace/all-page/all-page-header.tsx | 19 +- 59 files changed, 647 insertions(+), 821 deletions(-) diff --git a/packages/frontend/core/src/commands/affine-navigation.tsx b/packages/frontend/core/src/commands/affine-navigation.tsx index 026a8a707..0a3094c4b 100644 --- a/packages/frontend/core/src/commands/affine-navigation.tsx +++ b/packages/frontend/core/src/commands/affine-navigation.tsx @@ -6,7 +6,7 @@ import type { createStore } from 'jotai'; import { openSettingModalAtom, openWorkspaceListModalAtom } from '../atoms'; import type { useNavigateHelper } from '../hooks/use-navigate-helper'; -import { mixpanel, track } from '../mixpanel'; +import { track } from '../mixpanel'; import { registerAffineCommand } from './registry'; export function registerAffineNavigationCommands({ @@ -93,10 +93,7 @@ export function registerAffineNavigationCommands({ icon: , label: t['com.affine.cmdk.affine.navigation.open-account-settings'](), run() { - mixpanel.track('AccountSettingsViewed', { - // page: - segment: 'cmdk', - }); + track.$.cmdk.settings.openSettings({ to: 'account' }); store.set(openSettingModalAtom, s => ({ activeTab: 'account', open: !s.open, diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx index a33de6b29..5689e2875 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/edgeless.dialog.tsx @@ -1,6 +1,6 @@ import { Button, FlexWrapper, notify } from '@affine/component'; import { openSettingModalAtom } from '@affine/core/atoms'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { SubscriptionService } from '@affine/core/modules/cloud'; import { useI18n } from '@affine/i18n'; import { AiIcon } from '@blocksuite/icons/rc'; @@ -65,11 +65,7 @@ export const AIOnboardingEdgeless = () => { const mode = useLiveData(doc.mode$); const goToPricingPlans = useCallback(() => { - mixpanel.track('PlansViewed', { - page: 'whiteboard editor', - segment: 'ai onboarding', - module: 'whiteboard dialog', - }); + track.$.aiOnboarding.dialog.viewPlans(); setSettingModal({ open: true, activeTab: 'plans', diff --git a/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx b/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx index 85ca7ac6c..831145b2b 100644 --- a/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx +++ b/packages/frontend/core/src/components/affine/ai-onboarding/general.dialog.tsx @@ -1,7 +1,7 @@ import { Button, IconButton, Modal } from '@affine/component'; import { openSettingModalAtom } from '@affine/core/atoms'; import { useBlurRoot } from '@affine/core/hooks/use-blur-root'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { Trans, useI18n } from '@affine/i18n'; import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc'; @@ -116,11 +116,7 @@ export const AIOnboardingGeneral = () => { activeTab: 'plans', scrollAnchor: 'aiPricingPlan', }); - mixpanel.track('PlansViewed', { - page: 'whiteboard editor', - segment: 'ai onboarding', - module: 'general', - }); + track.$.aiOnboarding.dialog.viewPlans(); closeAndDismiss(); }, [closeAndDismiss, setSettingModal]); const onPrev = useCallback(() => { diff --git a/packages/frontend/core/src/components/affine/auth/oauth.tsx b/packages/frontend/core/src/components/affine/auth/oauth.tsx index af8babc2f..a0b78de0e 100644 --- a/packages/frontend/core/src/components/affine/auth/oauth.tsx +++ b/packages/frontend/core/src/components/affine/auth/oauth.tsx @@ -1,7 +1,7 @@ import { notify, Skeleton } from '@affine/component'; import { Button } from '@affine/component/ui/button'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { OAuthProviderType } from '@affine/graphql'; import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; @@ -75,7 +75,7 @@ function OAuthProvider({ notify.error({ title: 'Failed to sign in, please try again.' }); } finally { setIsConnecting(false); - mixpanel.track('OAuth', { provider }); + track.$.$.auth.oauth({ provider }); } }, [authService, provider, redirectUri]); diff --git a/packages/frontend/core/src/components/affine/auth/sign-in.tsx b/packages/frontend/core/src/components/affine/auth/sign-in.tsx index 31c245afc..a98ec6ad9 100644 --- a/packages/frontend/core/src/components/affine/auth/sign-in.tsx +++ b/packages/frontend/core/src/components/affine/auth/sign-in.tsx @@ -3,7 +3,7 @@ import { AuthInput, ModalHeader } from '@affine/component/auth-components'; import { Button } from '@affine/component/ui/button'; import { authAtom } from '@affine/core/atoms'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { Trans, useI18n } from '@affine/i18n'; import { ArrowRightBigIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; @@ -75,9 +75,7 @@ export const SignIn: FC = ({ if (hasPassword) { setAuthState('signInWithPassword'); } else { - mixpanel.track('SignIn', { - email, - }); + track.$.$.auth.signIn(); await authService.sendEmailMagicLink( email, verifyToken, @@ -93,9 +91,7 @@ export const SignIn: FC = ({ challenge, searchParams.get('redirect_uri') ); - mixpanel.track('SignUp', { - email, - }); + track.$.$.auth.signUp(); setAuthState('afterSignUpSendEmail'); } } diff --git a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx index a882c506a..212e3b167 100644 --- a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx +++ b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx @@ -1,5 +1,4 @@ import { Tooltip } from '@affine/component/ui/tooltip'; -import { mixpanel } from '@affine/core/mixpanel'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { useLiveData, useServices } from '@toeverything/infra'; @@ -44,10 +43,6 @@ export const UserPlanButton = () => { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - mixpanel.track('PlansViewed', { - segment: 'settings panel', - module: 'profile and badge', - }); }, [setSettingModalAtom] ); @@ -72,6 +67,7 @@ export const UserPlanButton = () => { data-is-believer={isBeliever ? 'true' : undefined} className={styles.userPlanButton} onClick={handleClick} + data-event-props="$.settingsPanel.profileAndBadge.viewPlans" > {planLabel} diff --git a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx index 5aae2dd9a..b4ee0157f 100644 --- a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx @@ -3,7 +3,7 @@ import type { ConfirmModalProps } from '@affine/component/ui/modal'; import { ConfirmModal, Modal } from '@affine/component/ui/modal'; import { authAtom } from '@affine/core/atoms'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { DebugLogger } from '@affine/debug'; import { apis } from '@affine/electron-api'; import { WorkspaceFlavour } from '@affine/env/workspace'; @@ -222,9 +222,7 @@ export const CreateWorkspaceModal = ({ const onConfirmName = useAsyncCallback( async (name: string, workspaceFlavour: WorkspaceFlavour) => { - mixpanel.track('CreateWorkspace', { - workspaceFlavour, - }); + track.$.$.$.createWorkspace({ flavour: workspaceFlavour }); if (loading) return; setLoading(true); diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index baaa5be84..64bb74535 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -4,7 +4,7 @@ import { Button, IconButton } from '@affine/component/ui/button'; import { Modal, useConfirmModal } from '@affine/component/ui/modal'; import { openSettingModalAtom } from '@affine/core/atoms'; import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { i18nTime, Trans, useI18n } from '@affine/i18n'; @@ -108,18 +108,17 @@ const HistoryEditorPreview = ({ mode, title, }: HistoryEditorPreviewProps) => { - const onSwitchToPageMode = useCallback(() => { - mixpanel.track('Button', { - resolve: 'HistorySwitchToPageMode', - }); - onModeChange('page'); - }, [onModeChange]); - const onSwitchToEdgelessMode = useCallback(() => { - mixpanel.track('Button', { - resolve: 'HistorySwitchToEdgelessMode', - }); - onModeChange('edgeless'); - }, [onModeChange]); + const { onSwitchToPageMode, onSwitchToEdgelessMode } = useMemo( + () => ({ + onSwitchToPageMode: () => { + onModeChange('page'); + }, + onSwitchToEdgelessMode: () => { + onModeChange('edgeless'); + }, + }), + [onModeChange] + ); const content = useMemo(() => { return ( @@ -129,11 +128,15 @@ const HistoryEditorPreview = ({ @@ -229,9 +232,7 @@ const PlanPrompt = () => { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - mixpanel.track('PlansViewed', { - segment: 'doc history', - }); + track.$.docHistory.$.viewPlans(); }, [setSettingModalAtom]); const t = useI18n(); @@ -562,9 +563,7 @@ export const GlobalPageHistoryModal = () => { const workspace = useService(WorkspaceService).workspace; const handleOpenChange = useCallback( (open: boolean) => { - mixpanel.track('Button', { - resolve: open ? 'OpenPageHistoryModal' : 'ClosePageHistoryModal', - }); + track.$.docHistory.$[open ? 'open' : 'close'](); setState(prev => ({ ...prev, open, diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx index 470be5706..8c874a22b 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx @@ -1,6 +1,6 @@ import { ConfirmModal } from '@affine/component/ui/modal'; import { openQuotaModalAtom, openSettingModalAtom } from '@affine/core/atoms'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { UserQuotaService } from '@affine/core/modules/cloud'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; @@ -50,11 +50,7 @@ export const CloudQuotaModal = () => { scrollAnchor: 'cloudPricingPlan', }); - mixpanel.track('PlansViewed', { - segment: 'payment wall', - category: 'payment wall storage', - }); - + track.$.paywall.storage.viewPlans(); setOpen(false); }, [setOpen, setSettingModalAtom]); diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx index 97c3e04fe..ec8f4b110 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/ai-usage-panel.tsx @@ -1,12 +1,13 @@ import { Button, ErrorMessage, Skeleton } from '@affine/component'; import { SettingRow } from '@affine/component/setting-components'; import { openSettingModalAtom } from '@affine/core/atoms'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { ServerConfigService, SubscriptionService, UserCopilotQuotaService, } from '@affine/core/modules/cloud'; +import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; @@ -47,12 +48,7 @@ export const AIUsagePanel = () => { open: true, activeTab: 'billing', }); - mixpanel.track('BillingViewed', { - segment: 'settings panel', - module: 'account usage list', - control: 'change plan button', - type: 'ai subscription', - }); + track.$.settingsPanel.accountUsage.viewPlans({ plan: SubscriptionPlan.AI }); }, [setOpenSettingModal]); if (loading) { @@ -104,7 +100,7 @@ export const AIUsagePanel = () => { > {copilotActionLimit === 'unlimited' ? ( hasPaymentFeature && aiSubscription?.canceledAt ? ( - + ) : ( diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx index 1dfbb5c88..20e25f51c 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx @@ -1,6 +1,6 @@ import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { SubscriptionPlan } from '@affine/graphql'; import { useLiveData, useService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; @@ -20,11 +20,9 @@ export const CancelAction = ({ children, open, onOpenChange, - module, }: { open: boolean; onOpenChange: (open: boolean) => void; - module: string; } & PropsWithChildren) => { const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const [isMutating, setIsMutating] = useState(false); @@ -35,14 +33,11 @@ export const CancelAction = ({ useEffect(() => { if (!open || !proSubscription) return; - mixpanel.track('PlanChangeStarted', { - segment: 'settings panel', - module, - control: 'cancel', - type: proSubscription.plan, - category: proSubscription.recurring, + track.$.settingsPanel.plans.cancelSubscription({ + plan: proSubscription.plan, + recurring: proSubscription.recurring, }); - }, [module, open, proSubscription]); + }, [open, proSubscription]); const downgrade = useAsyncCallback(async () => { try { @@ -57,10 +52,9 @@ export const CancelAction = ({ onOpenChange(false); const proSubscription = subscription.pro$.value; if (proSubscription) { - mixpanel.track('PlanChangeSucceeded', { - control: 'cancel', - type: proSubscription.plan, - category: proSubscription.recurring, + track.$.settingsPanel.plans.confirmCancelingSubscription({ + plan: proSubscription.plan, + recurring: proSubscription.recurring, }); } if (account && prevRecurring) { @@ -127,10 +121,9 @@ export const ResumeAction = ({ onOpenChange(false); const proSubscription = subscription.pro$.value; if (proSubscription) { - mixpanel.track('PlanChangeSucceeded', { - control: 'paying', - type: proSubscription.plan, - category: proSubscription.recurring, + track.$.settingsPanel.plans.confirmResumingSubscription({ + plan: proSubscription.plan, + recurring: proSubscription.recurring, }); } } finally { diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx index a0652f836..9e811c686 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx @@ -2,7 +2,7 @@ import { Button, type ButtonProps, useConfirmModal } from '@affine/component'; import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; import { getDowngradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { AuthService, SubscriptionService } from '@affine/core/modules/cloud'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; @@ -10,10 +10,7 @@ import { useService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { useState } from 'react'; -export interface AICancelProps extends ButtonProps { - module: string; -} -export const AICancel = ({ module, ...btnProps }: AICancelProps) => { +export const AICancel = (btnProps: ButtonProps) => { const t = useI18n(); const [isMutating, setMutating] = useState(false); const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); @@ -26,12 +23,9 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => { const cancel = useAsyncCallback(async () => { const aiSubscription = subscription.ai$.value; if (aiSubscription) { - mixpanel.track('PlanChangeStarted', { - module, - segment: 'settings panel', - control: 'cancel', - type: SubscriptionPlan.AI, - category: aiSubscription.recurring, + track.$.settingsPanel.plans.cancelSubscription({ + plan: SubscriptionPlan.AI, + recurring: aiSubscription.recurring, }); } openConfirmModal({ @@ -57,10 +51,9 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => { SubscriptionPlan.AI ); setIdempotencyKey(nanoid()); - mixpanel.track('PlanChangeSucceeded', { - segment: 'settings panel', - control: 'plan cancel action', - type: subscription.ai$.value?.plan, + track.$.settingsPanel.plans.confirmCancelingSubscription({ + plan: SubscriptionPlan.AI, + recurring: aiSubscription?.recurring, }); const account = authService.session.account$.value; const prevRecurring = subscription.ai$.value?.recurring; @@ -81,7 +74,6 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => { }, }); }, [ - module, subscription, openConfirmModal, t, diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx index b84cad8d0..7b0d9ef99 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx @@ -5,7 +5,7 @@ import { useConfirmModal, } from '@affine/component'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { SubscriptionService } from '@affine/core/modules/cloud'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; @@ -15,11 +15,7 @@ import { cssVar } from '@toeverything/theme'; import { nanoid } from 'nanoid'; import { useState } from 'react'; -export interface AIResumeProps extends ButtonProps { - module: string; -} - -export const AIResume = ({ module, ...btnProps }: AIResumeProps) => { +export const AIResume = (btnProps: ButtonProps) => { const t = useI18n(); const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const subscription = useService(SubscriptionService).subscription; @@ -31,12 +27,9 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => { const resume = useAsyncCallback(async () => { const aiSubscription = subscription.ai$.value; if (aiSubscription) { - mixpanel.track('PlanChangeStarted', { - module, - segment: 'settings panel', - control: 'paying', - type: aiSubscription.plan, - category: aiSubscription.recurring, + track.$.settingsPanel.plans.resumeSubscription({ + plan: SubscriptionPlan.AI, + recurring: aiSubscription.recurring, }); } @@ -58,10 +51,9 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => { SubscriptionPlan.AI ); if (aiSubscription) { - mixpanel.track('PlanChangeSucceeded', { - category: aiSubscription.recurring, - control: 'paying', - type: aiSubscription.plan, + track.$.settingsPanel.plans.confirmResumingSubscription({ + plan: aiSubscription.plan, + recurring: aiSubscription.recurring, }); } notify({ @@ -75,7 +67,7 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => { setIdempotencyKey(nanoid()); }, }); - }, [subscription, openConfirmModal, t, module, idempotencyKey]); + }, [subscription, openConfirmModal, t, idempotencyKey]); return ( @@ -276,12 +275,9 @@ export const Upgrade = ({ const upgrade = useAsyncCallback(async () => { setMutating(true); - mixpanel.track('PlanUpgradeStarted', { - segment: 'settings panel', - module: 'pricing plan list', - control: 'pricing plan action', - type: 'cloud pro subscription', - category: recurring, + track.$.settingsPanel.plans.checkout({ + plan: SubscriptionPlan.Pro, + recurring: recurring, }); const link = await subscriptionService.createCheckoutSession({ recurring, @@ -338,12 +334,9 @@ const ChangeRecurring = ({ const subscription = useService(SubscriptionService).subscription; const onStartChange = useCallback(() => { - mixpanel.track('PlanChangeStarted', { - segment: 'settings panel', - module: 'pricing plan list', - control: 'paying', - type: SubscriptionPlan.Pro, - category: to, + track.$.settingsPanel.plans.changeSubscriptionRecurring({ + plan: SubscriptionPlan.Pro, + recurring: to, }); setOpen(true); }, [to]); @@ -422,12 +415,9 @@ const ResumeButton = () => { setOpen(true); const pro = subscription.pro$.value; if (pro) { - mixpanel.track('PlanChangeStarted', { - segment: 'settings panel', - module: 'pricing plan list', - control: 'paying', - type: SubscriptionPlan.Pro, - category: pro.recurring, + track.$.settingsPanel.plans.resumeSubscription({ + plan: SubscriptionPlan.Pro, + recurring: pro.recurring, }); } }, [subscription.pro$.value]); diff --git a/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx index e72316671..2a18664d9 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx @@ -6,7 +6,7 @@ import { Avatar } from '@affine/component/ui/avatar'; import { Tooltip } from '@affine/component/ui/tooltip'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { AuthService } from '@affine/core/modules/cloud'; import { UserFeatureService } from '@affine/core/modules/cloud/services/user-feature'; import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant'; @@ -22,7 +22,13 @@ import { } from '@toeverything/infra'; import clsx from 'clsx'; import { useAtom } from 'jotai/react'; -import { Suspense, useCallback, useEffect, useMemo } from 'react'; +import { + type MouseEvent, + Suspense, + useCallback, + useEffect, + useMemo, +} from 'react'; import { authAtom } from '../../../../atoms'; import { UserPlanButton } from '../../auth/user-plan-button'; @@ -115,29 +121,30 @@ export const SettingSidebar = ({ const t = useI18n(); const loginStatus = useLiveData(useService(AuthService).session.status$); const generalList = useGeneralSettingList(); + const gotoTab = useCallback( + (e: MouseEvent) => { + const tab = e.currentTarget.dataset.eventArg; + if (!tab) return; + track.$.settingsPanel.menu.openSettings({ to: tab }); + onTabChange(tab as ActiveTab, null); + }, + [onTabChange] + ); const onAccountSettingClick = useCallback(() => { - mixpanel.track('AccountSettingsViewed', { - // page: - segment: 'settings panel', - module: 'settings menu', - control: 'menu item', - }); + track.$.settingsPanel.menu.openSettings({ to: 'account' }); onTabChange('account', null); }, [onTabChange]); const onWorkspaceSettingClick = useCallback( (subTab: WorkspaceSubTab, workspaceMetadata: WorkspaceMetadata) => { - mixpanel.track(`view workspace setting`, { - // page: - segment: 'settings panel', - module: 'settings menu', - control: 'menu item', - type: subTab, - workspaceId: workspaceMetadata.id, + track.$.settingsPanel.menu.openSettings({ + to: 'workspace', + control: subTab, }); onTabChange(`workspace:${subTab}`, workspaceMetadata); }, [onTabChange] ); + return (
@@ -155,24 +162,8 @@ export const SettingSidebar = ({ })} key={key} title={title} - onClick={() => { - if (key === 'billing') { - mixpanel.track('BillingViewed', { - // page: - segment: 'settings panel', - module: 'settings menu', - control: 'menu item', - }); - } else if (key === 'plans') { - mixpanel.track('PlansViewed', { - // page: - segment: 'settings panel', - module: 'settings menu', - control: 'menu item', - }); - } - onTabChange(key, null); - }} + data-event-arg={key} + onClick={gotoTab} data-testid={testId} > {icon({ className: 'icon' })} diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx index 8a42d8045..a97ed00ad 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx @@ -21,7 +21,7 @@ import { useMemberCount } from '@affine/core/hooks/affine/use-member-count'; import type { Member } from '@affine/core/hooks/affine/use-members'; import { useMembers } from '@affine/core/hooks/affine/use-members'; import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceFlavour } from '@affine/env/workspace'; @@ -146,11 +146,8 @@ export const CloudWorkspaceMembersPanel = () => { activeTab: 'plans', scrollAnchor: 'cloudPricingPlan', }); - mixpanel.track('PlansViewed', { - // page: - segment: 'settings panel', - module: 'workspace setting', - control: 'invite member', + track.$.settingsPanel.workspace.viewPlans({ + control: 'inviteMember', }); }, [setSettingModalAtom]); diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx index 5e7b228a0..1be710270 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-page.tsx @@ -4,7 +4,7 @@ import { Button } from '@affine/component/ui/button'; import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu'; import { useSharingUrl } from '@affine/core/hooks/affine/use-share-url'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { ServerConfigService } from '@affine/core/modules/cloud'; import { ShareService } from '@affine/core/modules/share-doc'; import { WorkspaceFlavour } from '@affine/env/workspace'; @@ -106,11 +106,8 @@ export const AffineSharePage = (props: ShareMenuProps) => { await shareService.share.enableShare( mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page ); - mixpanel.track('ShareCreated', { - segment: 'sharing panel', - module: 'public share', - control: 'share panel', - type: mode, + track.$.header.share.createShareLink({ + mode, }); notify.success({ title: diff --git a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx b/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx index d467dc8d7..153cc2f02 100644 --- a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx +++ b/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx @@ -1,4 +1,4 @@ -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { CloseIcon, DownloadIcon } from '@blocksuite/icons/rc'; import clsx from 'clsx'; import { useCallback, useState } from 'react'; @@ -21,9 +21,7 @@ export function AppDownloadButton({ // TODO(@JimmFly): unify this type of literal value. const handleClick = useCallback(() => { - mixpanel.track('Button', { - resolve: 'GoToDownloadAppPage', - }); + track.$.navigationPanel.bottomButtons.downloadApp(); const url = `https://affine.pro/download?channel=stable`; open(url, '_blank'); }, []); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx index 73efc8cdd..9a3bc0690 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/setup-provider.tsx @@ -2,7 +2,7 @@ import { notify } from '@affine/component'; import { authAtom, openSettingModalAtom } from '@affine/core/atoms'; import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { getBaseUrl, type getCopilotHistoriesQuery, @@ -470,10 +470,7 @@ Could you make a new website based on these notes and send back just the html fi activeTab: 'billing', open: true, }); - mixpanel.track('PlansViewed', { - segment: 'payment wall', - category: 'payment wall ai action count', - }); + track.$.paywall.aiAction.viewPlans(); }); AIProvider.slots.requestLogin.on(() => { diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/tracker.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/tracker.ts index 60b74b0ad..ec56834b1 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/tracker.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/tracker.ts @@ -1,5 +1,5 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai'; -import { mixpanel } from '@affine/core/mixpanel'; +import { mixpanel, track } from '@affine/core/mixpanel'; import type { EditorHost } from '@blocksuite/block-std'; import type { BlockModel } from '@blocksuite/store'; import { lowerCase, omit } from 'lodash-es'; @@ -13,7 +13,7 @@ type AIActionEventName = | 'AI result accepted'; type AIActionEventProperties = { - page: 'doc-editor' | 'whiteboard-editor'; + page: 'doc' | 'edgeless'; segment: | 'AI action panel' | 'right side bar' @@ -73,9 +73,7 @@ const trackAction = ({ }; const inferPageMode = (host: EditorHost) => { - return host.querySelector('affine-page-root') - ? 'doc-editor' - : 'whiteboard-editor'; + return host.querySelector('affine-page-root') ? 'doc' : 'edgeless'; }; const defaultActionOptions = [ @@ -253,14 +251,12 @@ const toTrackedOptions = ( export function setupTracker() { AIProvider.slots.requestUpgradePlan.on(() => { - mixpanel.track('AI', { - action: 'requestUpgradePlan', - }); + track.$.paywall.aiAction.viewPlans(); }); AIProvider.slots.requestLogin.on(() => { - mixpanel.track('AI', { - action: 'requestLogin', + track.$.$.auth.signIn({ + control: 'aiAction', }); }); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts index 8c659c228..0f030a301 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts @@ -1,4 +1,4 @@ -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties'; import { I18n, i18nTime } from '@affine/i18n'; import type { EditorHost } from '@blocksuite/block-std'; @@ -75,12 +75,7 @@ export function createLinkedWidgetConfig(framework: FrameworkProvider) { inlineEditor, docId: doc.id, }); - mixpanel.track('LinkedDocCreated', { - control: 'linked doc', - module: 'inline @', - type: 'doc', - other: 'existing doc', - }); + track.doc.editor.atMenu.linkDoc(); }, })), maxDisplay: MAX_DOCS, diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index b8dadca99..ede3febe5 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -7,7 +7,7 @@ import { toReactNode, type useConfirmModal, } from '@affine/component'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import { resolveLinkToDoc } from '@affine/core/modules/navigation'; import type { PeekViewService } from '@affine/core/modules/peek-view'; @@ -465,30 +465,11 @@ export function patchQuickSearchService( pageId: linkedDoc.id, }, ]); - const isEdgeless = - rootComponent instanceof EdgelessRootBlockComponent; if (result.isNewDoc) { - mixpanel.track('DocCreated', { - control: 'linked doc', - module: 'slash commands', - type: 'linked doc', - category: 'doc', - page: isEdgeless ? 'whiteboard editor' : 'doc editor', - }); - mixpanel.track('LinkedDocCreated', { - control: 'new doc', - module: 'slash commands', - type: 'doc', - page: isEdgeless ? 'whiteboard editor' : 'doc editor', - }); - } else { - mixpanel.track('LinkedDocCreated', { - control: 'linked doc', - module: 'slash commands', - type: 'doc', - page: isEdgeless ? 'whiteboard editor' : 'doc editor', - }); + track.doc.editor.slashMenu.createDoc({ control: 'linkDoc' }); + track.doc.editor.slashMenu.linkDoc({ control: 'createDoc' }); } + track.doc.editor.slashMenu.linkDoc({ control: 'linkDoc' }); } else if ('userInput' in result) { const embedOptions = service.getEmbedBlockOptions( result.userInput diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx index 1a84e8c26..d37b8ab81 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/menu/index.tsx @@ -18,7 +18,7 @@ import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud'; import { useExportPage } from '@affine/core/hooks/affine/use-export-page'; import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench/services/view'; import { useDetailPageHeaderResponsive } from '@affine/core/pages/workspace/detail-page/use-header-responsive'; @@ -157,36 +157,23 @@ export const PageHeaderMenuButton = ({ const handleDuplicate = useCallback(() => { duplicate(pageId); - mixpanel.track('DocCreated', { - segment: 'editor header', - page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor', - module: 'header menu', - control: 'copy doc', - type: 'doc duplicate', - category: 'doc', + track.$.header.actions.createDoc({ + control: 'duplicate', }); - }, [doc.mode$.value, duplicate, pageId]); + }, [duplicate, pageId]); const onImportFile = useAsyncCallback(async () => { const options = await importFile(); if (options.isWorkspaceFile) { - mixpanel.track('WorkspaceCreated', { - segment: 'editor header', - page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor', - module: 'header menu', - control: 'import button', - type: 'imported workspace', + track.$.header.actions.createWorkspace({ + control: 'import', }); } else { - mixpanel.track('DocCreated', { - segment: 'editor header', - page: doc.mode$.value === 'page' ? 'doc editor' : 'edgeless editor', - module: 'header menu', - control: 'import button', - type: 'imported doc', + track.$.header.actions.createDoc({ + control: 'import', }); } - }, [doc.mode$.value, importFile]); + }, [importFile]); const showResponsiveMenu = hideShare; const ResponsiveMenuItems = ( diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx index ed415112f..f6b88222a 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-mode-switch/index.tsx @@ -1,6 +1,6 @@ import { Tooltip } from '@affine/component/ui/tooltip'; import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { useI18n } from '@affine/i18n'; import { type DocMode, @@ -62,8 +62,8 @@ export const EditorModeSwitch = ({ }, [currentMode, isPublic, doc, pageId, t, trash]); const onSwitchToPageMode = useCallback(() => { - mixpanel.track('Button', { - resolve: 'SwitchToPageMode', + track.$.header.actions.switchPageMode({ + mode: 'page', }); if (currentMode === 'page' || isPublic) { return; @@ -73,8 +73,8 @@ export const EditorModeSwitch = ({ }, [currentMode, isPublic, doc, t]); const onSwitchToEdgelessMode = useCallback(() => { - mixpanel.track('Button', { - resolve: 'SwitchToEdgelessMode', + track.$.header.actions.switchPageMode({ + mode: 'edgeless', }); if (currentMode === 'edgeless' || isPublic) { return; diff --git a/packages/frontend/core/src/components/page-list/components/new-page-button.tsx b/packages/frontend/core/src/components/page-list/components/new-page-button.tsx index cea17cebd..d2ca4d548 100644 --- a/packages/frontend/core/src/components/page-list/components/new-page-button.tsx +++ b/packages/frontend/core/src/components/page-list/components/new-page-button.tsx @@ -1,6 +1,6 @@ import { DropdownButton, Menu } from '@affine/component'; import { BlockCard } from '@affine/component/card/block-card'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { useI18n } from '@affine/i18n'; import { EdgelessIcon, ImportIcon, PageIcon } from '@blocksuite/icons/rc'; import type { PropsWithChildren } from 'react'; @@ -70,26 +70,14 @@ export const NewPageButton = ({ const handleCreateNewPage = useCallback(() => { createNewPage(); setOpen(false); - mixpanel.track('DocCreated', { - page: 'doc library', - segment: 'all doc', - module: 'doc list header', - control: 'new doc button', - type: 'doc', - category: 'page', - }); + track.allDocs.header.actions.createDoc(); }, [createNewPage]); const handleCreateNewEdgeless = useCallback(() => { createNewEdgeless(); setOpen(false); - mixpanel.track('DocCreated', { - page: 'doc library', - segment: 'all doc', - module: 'doc list header', - control: 'new whiteboard button', - type: 'doc', - category: 'whiteboard', + track.allDocs.header.actions.createDoc({ + mode: 'edgeless', }); }, [createNewEdgeless]); diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx index 8cd3dd901..dea28f58d 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx @@ -7,7 +7,7 @@ import { } from '@affine/component'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import type { Collection } from '@affine/env/filter'; @@ -49,21 +49,12 @@ export const PageListHeader = () => { const onImportFile = useAsyncCallback(async () => { const options = await importFile(); if (options.isWorkspaceFile) { - mixpanel.track('WorkspaceCreated', { - page: 'doc library', - segment: 'all doc', - module: 'doc list header', - control: 'import button', - type: 'imported workspace', + track.allDocs.header.actions.createWorkspace({ + control: 'import', }); } else { - mixpanel.track('DocCreated', { - page: 'doc library', - segment: 'all doc', - module: 'doc list header', - control: 'import button', - type: 'imported doc', - // category + track.allDocs.header.actions.createDoc({ + control: 'import', }); } }, [importFile]); diff --git a/packages/frontend/core/src/components/page-list/operation-cell.tsx b/packages/frontend/core/src/components/page-list/operation-cell.tsx index d07d51c91..c853c3a48 100644 --- a/packages/frontend/core/src/components/page-list/operation-cell.tsx +++ b/packages/frontend/core/src/components/page-list/operation-cell.tsx @@ -9,7 +9,7 @@ import { import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper'; import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { FavoriteService } from '@affine/core/modules/favorite'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { WorkbenchService } from '@affine/core/modules/workbench'; @@ -115,13 +115,8 @@ export const PageOperationCell = ({ const onDuplicate = useCallback(() => { duplicate(page.id, false); - mixpanel.track('DocCreated', { - segment: 'all doc', - module: 'doc item menu', - control: 'copy doc', - type: 'doc duplicate', - category: 'doc', - page: 'doc library', + track.allDocs.list.docMenu.createDoc({ + control: 'duplicate', }); }, [duplicate, page.id]); diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx index ead2d981d..5733fe159 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx @@ -1,7 +1,7 @@ import { Loading } from '@affine/component'; import { Divider } from '@affine/component/ui/divider'; import { MenuItem } from '@affine/component/ui/menu'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { AuthService } from '@affine/core/modules/cloud'; import { useI18n } from '@affine/i18n'; import { Logo1Icon } from '@blocksuite/icons/rc'; @@ -25,9 +25,7 @@ export const SignInItem = () => { const t = useI18n(); const onClickSignIn = useCallback(() => { - mixpanel.track('Button', { - resolve: 'SignIn', - }); + track.$.navigationPanel.workspaceList.signIn(); setOpen(state => ({ ...state, openModal: true, @@ -92,9 +90,7 @@ const UserWithWorkspaceListInner = ({ if (!isAuthenticated && !runtimeConfig.allowLocalWorkspace) { return openSignInModal(); } - mixpanel.track('Button', { - resolve: 'NewWorkspace', - }); + track.$.navigationPanel.workspaceList.createWorkspace(); setOpenCreateWorkspaceModal('new'); onEventEnd?.(); }, [ @@ -104,9 +100,10 @@ const UserWithWorkspaceListInner = ({ setOpenCreateWorkspaceModal, ]); + track.$.navigationPanel.workspaceList.createWorkspace(); const onAddWorkspace = useCallback(() => { - mixpanel.track('Button', { - resolve: 'AddWorkspace', + track.$.navigationPanel.workspaceList.createWorkspace({ + control: 'import', }); setOpenCreateWorkspaceModal('add'); onEventEnd?.(); diff --git a/packages/frontend/core/src/components/root-app-sidebar/import-page.tsx b/packages/frontend/core/src/components/root-app-sidebar/import-page.tsx index 3b1bb22e8..95a3f1e3c 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/import-page.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/import-page.tsx @@ -1,5 +1,5 @@ import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { useI18n } from '@affine/i18n'; import { ImportIcon } from '@blocksuite/icons/rc'; @@ -13,22 +13,11 @@ const ImportPage = ({ docCollection }: { docCollection: DocCollection }) => { const onImportFile = useAsyncCallback(async () => { const options = await importFile(); - if (options.isWorkspaceFile) { - mixpanel.track('WorkspaceCreated', { - segment: 'navigation panel', - module: 'doc list header', - control: 'import button', - type: 'imported workspace', - }); - } else { - mixpanel.track('DocCreated', { - segment: 'navigation panel', - module: 'doc list header', - control: 'import button', - type: 'imported doc', - // category - }); - } + track.$.navigationPanel.workspaceList[ + options.isWorkspaceFile ? 'createWorkspace' : 'createDoc' + ]({ + control: 'import', + }); }, [importFile]); return ( diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 5c740525c..15ddc3e6f 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -1,6 +1,6 @@ import { openSettingModalAtom } from '@affine/core/atoms'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { ExplorerCollections, ExplorerFavorites, @@ -91,21 +91,14 @@ export const RootAppSidebar = (): ReactElement => { async (e?: MouseEvent) => { const page = pageHelper.createPage('page', false); page.load(); - mixpanel.track('DocCreated', { - segment: 'navigation panel', - module: 'bottom button', - control: 'new doc button', - category: 'page', - type: 'doc', - }); + track.$.navigationPanel.$.createDoc(); + workbench.openDoc(page.id, { at: e?.ctrlKey || e?.metaKey ? 'new-tab' : 'active', }); }, [pageHelper, workbench] ); - - // Listen to the "New Page" action from the menu useEffect(() => { if (environment.isDesktop) { return events?.applicationMenu.onNewPageAction(() => onClickNewPage()); @@ -120,12 +113,7 @@ export const RootAppSidebar = (): ReactElement => { activeTab: 'appearance', open: true, }); - mixpanel.track('SettingsViewed', { - // page: - segment: 'navigation panel', - module: 'general list', - control: 'settings button', - }); + track.$.navigationPanel.$.openSettings(); }, [setOpenSettingModalAtom]); const sidebarOpen = useAtomValue(appSidebarOpenAtom); @@ -153,7 +141,7 @@ export const RootAppSidebar = (): ReactElement => { diff --git a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx index fdf20e6d1..70410c79e 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx @@ -14,7 +14,7 @@ import { openSettingModalAtom, openSignOutModalAtom, } from '@affine/core/atoms'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { useI18n } from '@affine/i18n'; import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; @@ -83,12 +83,7 @@ const AccountMenu = () => { const setOpenSignOutModalAtom = useSetAtom(openSignOutModalAtom); const onOpenAccountSetting = useCallback(() => { - mixpanel.track('AccountSettingsViewed', { - // page: - segment: 'navigation panel', - module: 'profile and badge', - control: 'profile and email', - }); + track.$.navigationPanel.profileAndBadge.openSettings({ to: 'account' }); setSettingModalAtom(prev => ({ ...prev, open: true, diff --git a/packages/frontend/core/src/components/workspace-selector/index.tsx b/packages/frontend/core/src/components/workspace-selector/index.tsx index 5a728efc8..939c71b0d 100644 --- a/packages/frontend/core/src/components/workspace-selector/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/index.tsx @@ -1,5 +1,5 @@ import { Menu } from '@affine/component'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { useService, WorkspacesService } from '@toeverything/infra'; import { useAtom } from 'jotai'; import { useCallback, useEffect } from 'react'; @@ -16,9 +16,7 @@ export const WorkspaceSelector = () => { setOpenUserWorkspaceList(false); }, [setOpenUserWorkspaceList]); const openUserWorkspaceList = useCallback(() => { - mixpanel.track('Button', { - resolve: 'OpenWorkspaceList', - }); + track.$.navigationPanel.workspaceList.open(); setOpenUserWorkspaceList(true); }, [setOpenUserWorkspaceList]); diff --git a/packages/frontend/core/src/components/workspace-upgrade/upgrade.tsx b/packages/frontend/core/src/components/workspace-upgrade/upgrade.tsx index 24c36ced7..f05946fde 100644 --- a/packages/frontend/core/src/components/workspace-upgrade/upgrade.tsx +++ b/packages/frontend/core/src/components/workspace-upgrade/upgrade.tsx @@ -2,7 +2,7 @@ import { Button } from '@affine/component/ui/button'; import { AffineShapeIcon } from '@affine/core/components/page-list'; // TODO(@eyhn): import from page-list temporarily, need to defined common svg icon/images management. import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { WorkspaceSubPath } from '@affine/core/shared'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; @@ -26,9 +26,7 @@ export const WorkspaceUpgrade = function WorkspaceUpgrade() { return; } - mixpanel.track('Button', { - resolve: 'UpgradeWorkspace', - }); + track.workspace.$.$.upgradeWorkspace(); try { const newWorkspace = await currentWorkspace.upgrade.upgrade(); diff --git a/packages/frontend/core/src/hooks/affine/use-export-page.ts b/packages/frontend/core/src/hooks/affine/use-export-page.ts index dc35fa3e9..1f11173c1 100644 --- a/packages/frontend/core/src/hooks/affine/use-export-page.ts +++ b/packages/frontend/core/src/hooks/affine/use-export-page.ts @@ -3,7 +3,7 @@ import { pushGlobalLoadingEventAtom, resolveGlobalLoadingEventAtom, } from '@affine/component/global-loading'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { apis } from '@affine/electron-api'; import { useI18n } from '@affine/i18n'; import type { PageRootService, RootBlockModel } from '@blocksuite/blocks'; @@ -26,10 +26,8 @@ async function exportHandler({ page, type }: ExportHandlerOptions) { if (editorRoot) { pageService = editorRoot.spec.getService('affine:page'); } - mixpanel.track('ShareCreated', { + track.$.header.share.export({ type, - segment: 'sharing panel', - module: 'export share', }); switch (type) { case 'html': diff --git a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx index 0a9ed35e4..9d0b36a06 100644 --- a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -4,7 +4,7 @@ import { PreconditionStrategy, registerAffineCommand, } from '@affine/core/commands'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { WorkspaceFlavour } from '@affine/env/workspace'; import { useI18n } from '@affine/i18n'; @@ -161,10 +161,8 @@ export function useRegisterBlocksuiteEditorCommands() { label: t['com.affine.header.option.duplicate'](), run() { duplicate(docId); - mixpanel.track('DocCreated', { - control: 'cmdk', - type: 'doc duplicate', - category: 'doc', + track.$.cmdk.$.createDoc({ + control: 'duplicate', }); }, }) diff --git a/packages/frontend/core/src/hooks/affine/use-share-url.ts b/packages/frontend/core/src/hooks/affine/use-share-url.ts index fd711ea12..affec8cca 100644 --- a/packages/frontend/core/src/hooks/affine/use-share-url.ts +++ b/packages/frontend/core/src/hooks/affine/use-share-url.ts @@ -1,5 +1,5 @@ import { notify } from '@affine/component'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch'; import { useI18n } from '@affine/i18n'; import type { Disposable } from '@blocksuite/global/utils'; @@ -68,9 +68,8 @@ export const useSharingUrl = ({ .catch(err => { console.error(err); }); - mixpanel.track('ShareLinkCopied', { - module: urlType === 'share' ? 'public share' : 'private share', - type: 'link', + track.$.header.share.copyShareLink({ + type: urlType === 'share' ? 'public' : 'private', }); } else { notify.error({ diff --git a/packages/frontend/core/src/hooks/affine/use-subscription-notify.tsx b/packages/frontend/core/src/hooks/affine/use-subscription-notify.tsx index 09bddc38d..07dde9932 100644 --- a/packages/frontend/core/src/hooks/affine/use-subscription-notify.tsx +++ b/packages/frontend/core/src/hooks/affine/use-subscription-notify.tsx @@ -1,5 +1,5 @@ import { useUpgradeNotify } from '@affine/core/components/affine/subscription-landing/notify'; -import { mixpanel } from '@affine/core/mixpanel'; +import { track } from '@affine/core/mixpanel'; import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; import { nanoid } from 'nanoid'; import { useCallback, useEffect } from 'react'; @@ -129,11 +129,9 @@ export const useSubscriptionNotifyReader = () => { upgradeNotify(link); localStorage.removeItem(localStorageKey); - // mixpanel - mixpanel.track('PlanChangeSucceeded', { - category: recurring, - type: plan, - control: 'new subscription', + track.$.$.$.checkout({ + plan, + recurring, }); } catch (err) { console.error('Failed to parse subscription callback link', err); diff --git a/packages/frontend/core/src/hooks/use-app-updater.ts b/packages/frontend/core/src/hooks/use-app-updater.ts index 858a24122..89a0cf5a9 100644 --- a/packages/frontend/core/src/hooks/use-app-updater.ts +++ b/packages/frontend/core/src/hooks/use-app-updater.ts @@ -7,7 +7,7 @@ import { atomWithObservable, atomWithStorage } from 'jotai/utils'; import { useCallback, useState } from 'react'; import { Observable } from 'rxjs'; -import { mixpanel } from '../mixpanel'; +import { track } from '../mixpanel'; import { popupWindow } from '../utils'; import { useAsyncCallback } from './affine-async-hooks'; @@ -122,9 +122,7 @@ export const useAppUpdater = () => { ); const quitAndInstall = useCallback(() => { - mixpanel.track('Button', { - resolve: 'QuitAndInstall', - }); + track.$.navigationPanel.bottomButtons.quitAndInstall(); if (updateReady) { setAppQuitting(true); apis?.updater.quitAndInstall().catch(err => { @@ -135,9 +133,7 @@ export const useAppUpdater = () => { }, [updateReady]); const checkForUpdates = useCallback(async () => { - mixpanel.track('Button', { - resolve: 'CheckForUpdates', - }); + track.$.settingsPanel.about.checkUpdates(); if (checkingForUpdates) { return; } @@ -154,9 +150,7 @@ export const useAppUpdater = () => { }, [checkingForUpdates, setCheckingForUpdates]); const downloadUpdate = useCallback(() => { - mixpanel.track('Button', { - resolve: 'DownloadUpdate', - }); + track.$.settingsPanel.about.downloadUpdate(); apis?.updater.downloadUpdate().catch(err => { console.error('Error downloading update:', err); }); @@ -164,8 +158,8 @@ export const useAppUpdater = () => { const toggleAutoDownload = useCallback( (enable: boolean) => { - mixpanel.track('Button', { - resolve: 'ToggleAutoDownload', + track.$.settingsPanel.about.changeAppSetting({ + key: 'autoDownload', value: enable, }); setSetting({ @@ -177,8 +171,8 @@ export const useAppUpdater = () => { const toggleAutoCheck = useCallback( (enable: boolean) => { - mixpanel.track('Button', { - resolve: 'ToggleAutoCheck', + track.$.settingsPanel.about.changeAppSetting({ + key: 'autoCheckUpdates', value: enable, }); setSetting({ @@ -189,17 +183,13 @@ export const useAppUpdater = () => { ); const openChangelog = useAsyncCallback(async () => { - mixpanel.track('Button', { - resolve: 'OpenChangelog', - }); + track.$.navigationPanel.bottomButtons.openChangelog(); popupWindow(runtimeConfig.changelogUrl); await setChangelogUnread(true); }, [setChangelogUnread]); const dismissChangelog = useAsyncCallback(async () => { - mixpanel.track('Button', { - resolve: 'DismissChangelog', - }); + track.$.navigationPanel.bottomButtons.dismissChangelog(); await setChangelogUnread(true); }, [setChangelogUnread]); diff --git a/packages/frontend/core/src/mixpanel/auto.ts b/packages/frontend/core/src/mixpanel/auto.ts index ea6331333..24894f543 100644 --- a/packages/frontend/core/src/mixpanel/auto.ts +++ b/packages/frontend/core/src/mixpanel/auto.ts @@ -15,7 +15,7 @@ export function makeTracker(trackFn: TrackFn): CallableEventsChain { get(target, prop) { if ( typeof prop !== 'string' || - prop === '$$typeof' /* webpack hot load reading this prop */ + prop === '$$typeof' /* webpack hot-reload reads this prop */ ) { return undefined; } @@ -55,7 +55,12 @@ export function makeTracker(trackFn: TrackFn): CallableEventsChain { * @example * * ```html - *