From 5a196fc3c6bbfbac14a29437ace85599d4882f95 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Thu, 14 Aug 2025 20:17:43 -0600 Subject: [PATCH 1/7] Add analytics button --- src/components/ui/analytics-button.tsx | 35 +++++++++++ src/lib/analytics.ts | 71 +++++++++++++++++++++++ src/pages/StartFlowPage/StartFlowPage.tsx | 23 ++++++-- 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/components/ui/analytics-button.tsx create mode 100644 src/lib/analytics.ts diff --git a/src/components/ui/analytics-button.tsx b/src/components/ui/analytics-button.tsx new file mode 100644 index 00000000..5908a376 --- /dev/null +++ b/src/components/ui/analytics-button.tsx @@ -0,0 +1,35 @@ +import * as React from 'react' +import { Button, type ButtonProps } from './button' +import { AnalyticsService, type AnalyticsEvent, type AnalyticsEventProperties } from '@/lib/analytics' + +export interface AnalyticsButtonProps extends ButtonProps { + analyticsEvent?: AnalyticsEvent + analyticsProperties?: AnalyticsEventProperties +} + +const AnalyticsButton = React.forwardRef( + ({ analyticsEvent, analyticsProperties, onClick, ...props }, ref) => { + const handleClick = (e: React.MouseEvent) => { + // Track the analytics event if provided + if (analyticsEvent) { + AnalyticsService.trackEvent(analyticsEvent, analyticsProperties) + } + + // Call the original onClick handler + if (onClick) { + onClick(e) + } + } + + return ( + + From 3814946adcb414fdea95499d5e90374dc3060ad7 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Thu, 14 Aug 2025 20:19:36 -0600 Subject: [PATCH 2/7] add more analytics buttons --- src/components/ui/analytics-button.tsx | 8 +++---- src/pages/FocusSchedulePage.tsx | 31 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/components/ui/analytics-button.tsx b/src/components/ui/analytics-button.tsx index 5908a376..75efd75f 100644 --- a/src/components/ui/analytics-button.tsx +++ b/src/components/ui/analytics-button.tsx @@ -3,17 +3,15 @@ import { Button, type ButtonProps } from './button' import { AnalyticsService, type AnalyticsEvent, type AnalyticsEventProperties } from '@/lib/analytics' export interface AnalyticsButtonProps extends ButtonProps { - analyticsEvent?: AnalyticsEvent + analyticsEvent: AnalyticsEvent analyticsProperties?: AnalyticsEventProperties } const AnalyticsButton = React.forwardRef( ({ analyticsEvent, analyticsProperties, onClick, ...props }, ref) => { const handleClick = (e: React.MouseEvent) => { - // Track the analytics event if provided - if (analyticsEvent) { - AnalyticsService.trackEvent(analyticsEvent, analyticsProperties) - } + // Track the analytics event + AnalyticsService.trackEvent(analyticsEvent, analyticsProperties) // Call the original onClick handler if (onClick) { diff --git a/src/pages/FocusSchedulePage.tsx b/src/pages/FocusSchedulePage.tsx index 4120dee0..3284d117 100644 --- a/src/pages/FocusSchedulePage.tsx +++ b/src/pages/FocusSchedulePage.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { Layout } from '@/components/Layout' import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Card, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' import { useFocusSchedulesWithWorkflow, useDeleteFocusSchedule } from '@/api/hooks/useFocusSchedule' @@ -76,10 +77,18 @@ export default function FocusSchedulePage() { Schedule focus sessions to protect your most productive times

- + )}
@@ -104,20 +113,30 @@ export default function FocusSchedulePage() {
- - +
From 7e5f24444f9eed36390b6074af7868f17beec552 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Thu, 14 Aug 2025 20:24:08 -0600 Subject: [PATCH 3/7] more buttons --- .eslintrc.cjs | 17 ++++++++++++++-- src/pages/FocusSchedulePage.tsx | 35 ++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 804538a3..4cc82e2d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -55,7 +55,7 @@ module.exports = { { group: ['../pages/**', '../../pages/**', '../../../pages/**'], message: 'Use @/pages/* instead of relative paths' - } + }, ] }], }, @@ -84,7 +84,7 @@ module.exports = { { "group": ["../db/**", "../../db/**"], "message": "Architecture: No direct DB imports from components. Import a hook/API instead." - } + }, ] }] } @@ -128,6 +128,19 @@ module.exports = { ] }] } + }, + { + files: ["src/pages/**/*.{ts,tsx}"], + rules: { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": ["@/components/ui/button"], + "message": "Pages should use AnalyticsButton from @/components/ui/analytics-button instead of Button to ensure proper user action tracking" + } + ] + }] + } } ], } diff --git a/src/pages/FocusSchedulePage.tsx b/src/pages/FocusSchedulePage.tsx index 3284d117..299dfbeb 100644 --- a/src/pages/FocusSchedulePage.tsx +++ b/src/pages/FocusSchedulePage.tsx @@ -1,6 +1,5 @@ import { useState } from 'react' import { Layout } from '@/components/Layout' -import { Button } from '@/components/ui/button' import { AnalyticsButton } from '@/components/ui/analytics-button' import { Card, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' @@ -149,10 +148,16 @@ export default function FocusSchedulePage() {

Create your first focus schedule to start protecting your productive time.

- + )} @@ -183,8 +188,28 @@ export default function FocusSchedulePage() { - - + setShowDeleteDialog(false)} + analyticsEvent="delete_schedule_clicked" + analyticsProperties={{ + button_location: 'delete_dialog', + context: 'cancel' + }} + > + Cancel + + + Delete + From 4848a01709b1136df04d20f1177255d8f07dbc84 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Thu, 14 Aug 2025 20:49:42 -0600 Subject: [PATCH 4/7] additional analytics added --- src/lib/analytics.ts | 32 ++++++++++++++ src/pages/AccessibilityPage.tsx | 8 ++-- src/pages/CategoryDashboardPage.tsx | 11 +++-- src/pages/DeviceLimitPage.tsx | 14 +++++- src/pages/FeedbackPage.tsx | 11 +++-- src/pages/FlowPage/FlowPage.tsx | 17 ++++--- src/pages/FlowPage/Timer.tsx | 11 +++-- .../FriendsAnalyticsPreview.tsx | 20 ++++++--- src/pages/HomePage.tsx | 11 +++-- src/pages/LoginPage.tsx | 20 ++++++--- .../Integrations/IntegrationSettings.tsx | 44 ++++++++++++++++--- .../Integrations/SlackSettings.tsx | 20 ++++++--- src/pages/SettingsPage/SettingsPage.tsx | 20 ++++++--- src/pages/ShortcutTutorialPage.tsx | 11 +++-- src/pages/SlackOnboardingPage.tsx | 20 ++++++--- 15 files changed, 209 insertions(+), 61 deletions(-) diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts index 1cad37ed..659a28c5 100644 --- a/src/lib/analytics.ts +++ b/src/lib/analytics.ts @@ -5,6 +5,7 @@ export type AnalyticsEvent = | 'start_focus_clicked' | 'end_early_clicked' | 'focus_session_completed' + | 'add_time_clicked' // Schedule Management Events | 'create_schedule_clicked' @@ -30,6 +31,37 @@ export type AnalyticsEvent = | 'get_pro_clicked' | 'paywall_shown' + // Onboarding Events + | 'accessibility_enabled' + | 'shortcut_tutorial_completed' + + // Category Dashboard Events + | 'date_picker_clicked' + + // Login Events + | 'login_clicked' + | 'login_skipped' + + // Slack Onboarding Events + | 'slack_connect_clicked' + | 'slack_connect_skipped' + + // Settings Page Events + | 'delete_account_clicked' + | 'delete_account_clicked_canceled' + | 'delete_account_clicked_confirmed' + + // Integration Settings Events + | 'spotify_disconnect_clicked' + | 'spotify_connect_clicked' + | 'slack_configure_clicked' + | 'music_disconnect_clicked_canceled' + | 'music_disconnect_clicked_confirmed' + + // Friends Analytics Events + | 'offline_indicator_clicked' + | 'connect_to_friends_clicked' + export interface AnalyticsEventProperties { // Focus session properties difficulty?: 'easy' | 'medium' | 'hard' diff --git a/src/pages/AccessibilityPage.tsx b/src/pages/AccessibilityPage.tsx index a7f8e938..f80970cc 100644 --- a/src/pages/AccessibilityPage.tsx +++ b/src/pages/AccessibilityPage.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { useNavigate } from 'react-router-dom' import { useState, useEffect } from 'react' import { Github, Lock, Loader2, CheckCircle2 } from 'lucide-react' @@ -138,15 +138,17 @@ export const AccessibilityPage = () => {
- +
{(permissionStatus === 'checking' || permissionStatus === 'not_granted') && ( diff --git a/src/pages/CategoryDashboardPage.tsx b/src/pages/CategoryDashboardPage.tsx index e0b37a95..2b78c381 100644 --- a/src/pages/CategoryDashboardPage.tsx +++ b/src/pages/CategoryDashboardPage.tsx @@ -1,6 +1,6 @@ import { CSSProperties, useEffect, useState } from 'react' import { Layout } from '@/components/Layout' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Progress } from '@/components/ui/progress' import { Calendar } from '@/components/ui/calendar' import { formatTime } from '@/components/UsageSummary' @@ -215,9 +215,14 @@ export default function CategoryDashboardPage () { - + - + + upgrade to Ebb Pro + {' '} for up to 3 macOS devices. The first registered device is always active and free. diff --git a/src/pages/FeedbackPage.tsx b/src/pages/FeedbackPage.tsx index c20afa68..183e2d5e 100644 --- a/src/pages/FeedbackPage.tsx +++ b/src/pages/FeedbackPage.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from 'react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Card } from '@/components/ui/card' import { Layout } from '@/components/Layout' import { CommunityCard } from '@/components/CommunityCard' @@ -99,9 +99,14 @@ export default function FeedbackPage() { style={{ resize: 'none', overflow: 'hidden' }} />
- +
)} diff --git a/src/pages/FlowPage/FlowPage.tsx b/src/pages/FlowPage/FlowPage.tsx index 6e477a5c..034c4e00 100644 --- a/src/pages/FlowPage/FlowPage.tsx +++ b/src/pages/FlowPage/FlowPage.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { FlowSession } from '@/db/ebb/flowSessionRepo' import { DateTime, Duration } from 'luxon' import { FlowSessionApi } from '@/api/ebbApi/flowSessionApi' @@ -399,33 +399,36 @@ export const FlowPage = () => {
- - - +
) diff --git a/src/pages/FlowPage/Timer.tsx b/src/pages/FlowPage/Timer.tsx index 0bc5a4d3..8df65842 100644 --- a/src/pages/FlowPage/Timer.tsx +++ b/src/pages/FlowPage/Timer.tsx @@ -5,7 +5,7 @@ import { useState, useEffect } from 'react' import { FlowSession } from '@/db/ebb/flowSessionRepo' import { useFlowTimer } from '@/lib/stores/flowTimer' import { logAndToastError } from '@/lib/utils/ebbError.util' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' const getDurationFormatFromSeconds = (seconds: number) => { const duration = Duration.fromMillis(seconds * 1000) @@ -95,12 +95,17 @@ export const Timer = ({ flowSession }: { flowSession: FlowSession | null }) => { {time} {flowSession?.duration && ( - + )} ) diff --git a/src/pages/FriendsAnalyticsPage/FriendsAnalyticsPreview.tsx b/src/pages/FriendsAnalyticsPage/FriendsAnalyticsPreview.tsx index 872e3538..fcdc0eba 100644 --- a/src/pages/FriendsAnalyticsPage/FriendsAnalyticsPreview.tsx +++ b/src/pages/FriendsAnalyticsPage/FriendsAnalyticsPreview.tsx @@ -8,7 +8,7 @@ import { useNetworkStore } from '@/lib/stores/networkStore' import { useUpdateProfileLocation, useProfile } from '@/api/hooks/useProfile' import { logAndToastError } from '@/lib/utils/ebbError.util' import { ConnectIcon } from '@/components/icons/ConnectIcon' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { DateTime } from 'luxon' @@ -349,15 +349,20 @@ export const FriendsAnalyticsPreview = () => {

Check your internet connection to connect with friends and see detailed analytics.

- + ) : ( <> @@ -365,15 +370,20 @@ export const FriendsAnalyticsPreview = () => {

Connect with the community to unlock detailed analytics, compare with friends, and track your progress!

- + )} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index dce42934..58e32f3e 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,5 +1,5 @@ import { Layout } from '@/components/Layout' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { ChevronDown } from 'lucide-react' import { DateTime } from 'luxon' import { @@ -55,9 +55,14 @@ export const HomePage = () => { - + {

Focus starts here.

{error &&

{error}

} - + - +
diff --git a/src/pages/SettingsPage/Integrations/IntegrationSettings.tsx b/src/pages/SettingsPage/Integrations/IntegrationSettings.tsx index 16ec56cd..b633eb32 100644 --- a/src/pages/SettingsPage/Integrations/IntegrationSettings.tsx +++ b/src/pages/SettingsPage/Integrations/IntegrationSettings.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { SpotifyIcon } from '@/components/icons/SpotifyIcon' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { SpotifyAuthService } from '@/lib/integrations/spotify/spotifyAuth' import { SpotifyApiService } from '@/lib/integrations/spotify/spotifyApi' import { logAndToastError } from '@/lib/utils/ebbError.util' @@ -121,25 +121,35 @@ export const IntegrationSettings = () => {
{activeService === 'spotify' ? ( - + ) : ( - + {activeService === 'apple' && ( @@ -165,8 +175,28 @@ export const IntegrationSettings = () => { - - + setShowUnlinkDialog(false)} + analyticsEvent="music_disconnect_clicked_canceled" + analyticsProperties={{ + destination: 'cancel_disconnect', + source: 'integration_settings' + }} + > + Cancel + + + Disconnect + diff --git a/src/pages/SettingsPage/Integrations/SlackSettings.tsx b/src/pages/SettingsPage/Integrations/SlackSettings.tsx index c696c426..a3e8891a 100644 --- a/src/pages/SettingsPage/Integrations/SlackSettings.tsx +++ b/src/pages/SettingsPage/Integrations/SlackSettings.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { SlackIcon } from '@/components/icons/SlackIcon' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { SlackDisconnectModal } from '@/components/SlackDisconnectModal' import { useSlackStatus } from '@/api/hooks/useSlack' @@ -73,26 +73,36 @@ export const SlackSettings = () => {
{slackStatus?.workspaces?.length || 0 > 0 ? ( - + ) : ( - + diff --git a/src/pages/SettingsPage/SettingsPage.tsx b/src/pages/SettingsPage/SettingsPage.tsx index 334c3735..26ee787f 100644 --- a/src/pages/SettingsPage/SettingsPage.tsx +++ b/src/pages/SettingsPage/SettingsPage.tsx @@ -1,7 +1,7 @@ import { Layout } from '@/components/Layout' import { ModeToggle } from '@/components/ModeToggle' import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Dialog, @@ -198,12 +198,14 @@ export function SettingsPage() {
- +
@@ -255,20 +257,24 @@ export function SettingsPage() { - - + diff --git a/src/pages/ShortcutTutorialPage.tsx b/src/pages/ShortcutTutorialPage.tsx index a60be939..2c1dd0db 100644 --- a/src/pages/ShortcutTutorialPage.tsx +++ b/src/pages/ShortcutTutorialPage.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { useNavigate } from 'react-router-dom' import { OnboardingUtils } from '@/lib/utils/onboarding.util' import { ShortcutInput } from '@/components/ShortcutInput' @@ -52,13 +52,18 @@ export const ShortcutTutorialPage = () => {

- + ) } diff --git a/src/pages/SlackOnboardingPage.tsx b/src/pages/SlackOnboardingPage.tsx index 406e0c7c..24edbee0 100644 --- a/src/pages/SlackOnboardingPage.tsx +++ b/src/pages/SlackOnboardingPage.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { useNavigate } from 'react-router-dom' import { SlackIcon } from '@/components/icons/SlackIcon' import { OnboardingUtils } from '@/lib/utils/onboarding.util' @@ -35,22 +35,32 @@ export const SlackOnboardingPage = () => {
- + - +
{!user && ( From 539e37f7b02d325bb5b820140276b1745960ed84 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Sun, 17 Aug 2025 12:05:14 -0600 Subject: [PATCH 5/7] tmp --- .eslintrc.cjs | 2 +- src/components/ActiveDevicesSettings.tsx | 11 +++- src/components/AppSelector.tsx | 29 +++++++--- src/components/FriendsComparisonCard.tsx | 57 +++++++++++++------ src/components/ModeToggle.tsx | 8 +-- src/components/NotificationBanner.tsx | 7 ++- .../NotificationPanel/ActionButton.tsx | 12 ++-- .../NotificationPanel/NotificationPanel.tsx | 4 +- src/components/RangeModeSelector.tsx | 12 ++-- src/components/ui/analytics-button.tsx | 1 + src/lib/analytics.ts | 32 +++++++++++ 11 files changed, 130 insertions(+), 45 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4cc82e2d..b41e6b5d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -130,7 +130,7 @@ module.exports = { } }, { - files: ["src/pages/**/*.{ts,tsx}"], + files: ["src/pages/**/*.{ts,tsx}", "src/components/**/*.{ts,tsx}"], rules: { "no-restricted-imports": ["error", { "patterns": [ diff --git a/src/components/ActiveDevicesSettings.tsx b/src/components/ActiveDevicesSettings.tsx index f39352f0..145c1165 100644 --- a/src/components/ActiveDevicesSettings.tsx +++ b/src/components/ActiveDevicesSettings.tsx @@ -1,5 +1,5 @@ import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { useEffect, useState } from 'react' import { hostname } from '@tauri-apps/plugin-os' import { User } from '@supabase/supabase-js' @@ -149,13 +149,18 @@ export function ActiveDevicesSettings({ user, maxDevices }: ActiveDevicesSetting This Device ) : ( - + )}
diff --git a/src/components/AppSelector.tsx b/src/components/AppSelector.tsx index 130bb4f8..6ecab48e 100644 --- a/src/components/AppSelector.tsx +++ b/src/components/AppSelector.tsx @@ -6,7 +6,7 @@ import { AppCategory, categoryEmojis } from '@/lib/app-directory/apps-types' import { useEffect, useRef, useState } from 'react' import { MonitorApi , App, Tag } from '@/api/monitorApi/monitorApi' import { AppIcon } from '@/components/AppIcon' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { DifficultySelector } from '@/components/difficulty-selector' import { CategoryTooltip } from '@/components/CategoryTooltip' import { PaywallDialog } from '@/components/PaywallDialog' @@ -514,7 +514,7 @@ export function AppSelector({ {onIsAllowListChange && (
- + {!canUseAllowList ? ( - + ) : ( - + )}
)} diff --git a/src/components/FriendsComparisonCard.tsx b/src/components/FriendsComparisonCard.tsx index 30792020..d94fb181 100644 --- a/src/components/FriendsComparisonCard.tsx +++ b/src/components/FriendsComparisonCard.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Badge } from '@/components/ui/badge' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Users, UserPlus, Mail, Send, Check, X } from 'lucide-react' @@ -173,7 +173,8 @@ const PendingInvitesTab = () => {
Email: {invite.from_auth_user_email || invite.to_email}
- - +
))} {receivedRequests.length > visibleReceived && (
- +
)} @@ -232,14 +235,15 @@ const PendingInvitesTab = () => { {sentRequests.length > visibleSent && (
- +
)} @@ -295,9 +299,16 @@ const EmptyFriendsState = () => { required disabled={isInviting} /> - + @@ -358,10 +369,16 @@ export const FriendsComparisonCard = ({ }

- + @@ -429,17 +446,23 @@ export const FriendsComparisonCard = ({ />
- - +
diff --git a/src/components/ModeToggle.tsx b/src/components/ModeToggle.tsx index eae74038..6585eac9 100644 --- a/src/components/ModeToggle.tsx +++ b/src/components/ModeToggle.tsx @@ -1,6 +1,6 @@ import { Moon, Sun } from 'lucide-react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { DropdownMenu, DropdownMenuContent, @@ -15,11 +15,11 @@ export function ModeToggle() { return ( - + Toggle theme + setTheme('light')}> diff --git a/src/components/NotificationBanner.tsx b/src/components/NotificationBanner.tsx index e0edf513..d8ba9fa2 100644 --- a/src/components/NotificationBanner.tsx +++ b/src/components/NotificationBanner.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useGetActiveNotification, useUpdateNotificationStatus } from '@/api/hooks/useNotifications' import { useEffect } from 'react' import { useNavigate } from 'react-router-dom' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from './ui/analytics-button' interface NotificationAction { label: string @@ -51,14 +51,15 @@ export const NotificationBanner: React.FC = () => { {activeNotification.content}
{notificationAction && ( - + )}
)} - + ) } diff --git a/src/components/NotificationPanel/NotificationPanel.tsx b/src/components/NotificationPanel/NotificationPanel.tsx index 2ef2405f..bab394fc 100644 --- a/src/components/NotificationPanel/NotificationPanel.tsx +++ b/src/components/NotificationPanel/NotificationPanel.tsx @@ -14,8 +14,9 @@ import { EbbListen } from '@/lib/ebbListen' import { useShortcutStore } from '@/lib/stores/shortcutStore' import { useShortcutKeyDetection } from '../../hooks/useShortcutKeyDetection' import { EbbWorker } from '../../lib/ebbWorker' +import { AnalyticsEvent } from '../../lib/analytics' -type NotificationType = 'session-start' | 'quick-start' | 'smart-start-suggestion' | 'doomscroll-start-suggestion' | 'blocked-app' | 'blocked-app-hard' | 'session-end' | 'session-warning' | 'end-session' | 'scheduled-session-reminder' | 'scheduled-session-start' +export type NotificationType = 'session-start' | 'quick-start' | 'smart-start-suggestion' | 'doomscroll-start-suggestion' | 'blocked-app' | 'blocked-app-hard' | 'session-end' | 'session-warning' | 'end-session' | 'scheduled-session-reminder' | 'scheduled-session-start' interface NotificationPayload { timeCreating?: number @@ -485,6 +486,7 @@ export const NotificationPanel = () => { shortcutParts={shortcutParts} pressedKeys={pressedKeys} onClick={handleButtonClick} + analyticsEvent={`notification_${notificationType}_button_clicked` as AnalyticsEvent} /> )} diff --git a/src/components/RangeModeSelector.tsx b/src/components/RangeModeSelector.tsx index 6aef78ea..6ad6b7c9 100644 --- a/src/components/RangeModeSelector.tsx +++ b/src/components/RangeModeSelector.tsx @@ -3,7 +3,7 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/popover' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { cn } from '@/lib/utils/tailwind.util' export type RangeMode = 'day' | 'week' | 'month' @@ -31,7 +31,8 @@ export function RangeModeSelector({ value, onChange, className }: RangeModeSelec return ( - +
{rangeOptions.map((option) => ( - + ))}
diff --git a/src/components/ui/analytics-button.tsx b/src/components/ui/analytics-button.tsx index 75efd75f..955cdaf6 100644 --- a/src/components/ui/analytics-button.tsx +++ b/src/components/ui/analytics-button.tsx @@ -10,6 +10,7 @@ export interface AnalyticsButtonProps extends ButtonProps { const AnalyticsButton = React.forwardRef( ({ analyticsEvent, analyticsProperties, onClick, ...props }, ref) => { const handleClick = (e: React.MouseEvent) => { + console.log('AnalyticsButton clicked:', { analyticsEvent, analyticsProperties }) // Track the analytics event AnalyticsService.trackEvent(analyticsEvent, analyticsProperties) diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts index 659a28c5..abb5a317 100644 --- a/src/lib/analytics.ts +++ b/src/lib/analytics.ts @@ -1,4 +1,5 @@ import { default as posthog } from 'posthog-js' +import { NotificationType } from '@/components/NotificationPanel/NotificationPanel' export type AnalyticsEvent = // Focus Session Events @@ -6,6 +7,8 @@ export type AnalyticsEvent = | 'end_early_clicked' | 'focus_session_completed' | 'add_time_clicked' + | 'allow_list_clicked' + | 'block_list_clicked' // Schedule Management Events | 'create_schedule_clicked' @@ -61,6 +64,34 @@ export type AnalyticsEvent = // Friends Analytics Events | 'offline_indicator_clicked' | 'connect_to_friends_clicked' + | 'accept_friend_request_clicked' + | 'decline_friend_request_clicked' + | 'load_more_received_invites_clicked' + | 'load_more_sent_invites_clicked' + | 'invite_friends_clicked' + | 'invite_friends_canceled' + | 'invite_friends_sent' + + // Active Devices Settings Events + | 'deactivate_device_clicked' + + // Mode Toggle Events + | 'mode_toggle_clicked' + + // Notification Banner Events + | 'notification_dismissed' + | NotificationType + + // Range Mode Events + | 'range_mode_selector_clicked' + | 'range_mode_clicked' + + // Schedule Session Modal Events + | 'schedule_session_modal_clicked' + | 'schedule_session_modal_canceled' + | 'schedule_session_modal_created' + | 'schedule_session_modal_updated' + | 'schedule_session_modal_deleted' export interface AnalyticsEventProperties { // Focus session properties @@ -87,6 +118,7 @@ const trackEvent = ( event: AnalyticsEvent, properties?: AnalyticsEventProperties ): void => { + console.log('Tracking event:', event, properties) try { posthog.capture(event, { ...properties, From 80e54716975a435ab97b11da954dda6468a804e8 Mon Sep 17 00:00:00 2001 From: Paul Hovley Date: Mon, 18 Aug 2025 21:17:14 -0600 Subject: [PATCH 6/7] add additional analytics events --- src/components/ScheduleSessionModal.tsx | 24 +++--- src/components/ShortcutInput.tsx | 7 +- src/components/Sidebar.tsx | 30 ++++---- src/components/SlackFocusToggle.tsx | 12 +-- src/components/SocialStatusSummary.tsx | 7 +- src/components/TimeSelector.tsx | 7 +- src/components/TopNav.tsx | 21 +++-- src/components/TypewriterModeToggle.tsx | 77 ------------------- src/components/UsageSummary.tsx | 7 +- src/components/UserProfileSettings.tsx | 24 ++++-- src/components/developer/ResetAppData.tsx | 10 +-- .../difficulty-selector/DifficultyButton.tsx | 12 +-- .../DifficultySelector.tsx | 7 +- src/components/ui/calendar.tsx | 1 + src/components/ui/no-analytics-button.tsx | 19 +++++ src/lib/analytics.ts | 29 +++++++ 16 files changed, 148 insertions(+), 146 deletions(-) delete mode 100644 src/components/TypewriterModeToggle.tsx create mode 100644 src/components/ui/no-analytics-button.tsx diff --git a/src/components/ScheduleSessionModal.tsx b/src/components/ScheduleSessionModal.tsx index 41d604e7..ac61eb04 100644 --- a/src/components/ScheduleSessionModal.tsx +++ b/src/components/ScheduleSessionModal.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { DialogFooter } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' @@ -8,6 +8,7 @@ import { useGetWorkflows } from '@/api/hooks/useWorkflow' import { RecurrenceSettings } from '@/api/ebbApi/focusScheduleApi' import { cn } from '@/lib/utils/tailwind.util' import { Workflow } from '@/api/ebbApi/workflowApi' +import { NoAnalyticsButton } from './ui/no-analytics-button' interface ScheduleSessionModalProps { scheduleId?: string @@ -151,7 +152,7 @@ export function ScheduleSessionModal({ scheduleId, onClose }: ScheduleSessionMod
- - +
{recurrenceType === 'weekly' && ( @@ -180,7 +181,7 @@ export function ScheduleSessionModal({ scheduleId, onClose }: ScheduleSessionMod
{dayNames.map((day, index) => ( - + ))}
@@ -207,19 +208,20 @@ export function ScheduleSessionModal({ scheduleId, onClose }: ScheduleSessionMod - - + ) diff --git a/src/components/ShortcutInput.tsx b/src/components/ShortcutInput.tsx index 5250ee0c..c90c6a49 100644 --- a/src/components/ShortcutInput.tsx +++ b/src/components/ShortcutInput.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Hotkey } from '@/components/ui/hotkey' import { X, Check } from 'lucide-react' @@ -166,7 +166,8 @@ export function ShortcutInput({ popoverAlign = 'center' }: ShortcutInputProps) { return ( - + {snapshot?.isClosing && ( diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 139d2a29..8f31bbe1 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { NoAnalyticsButton } from '@/components/ui/no-analytics-button' import { Link, useLocation } from 'react-router-dom' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { SettingsGearIcon } from '@/components/icons/GearIcon' @@ -29,7 +29,7 @@ export function Sidebar() { {/* Category dashboard link */} - + Today @@ -46,7 +46,7 @@ export function Sidebar() { {isFocusScheduleFeatureEnabled(user?.email) && ( - + Focus Schedule )} - + Categories - + Friends @@ -98,7 +98,7 @@ export function Sidebar() { - + Community @@ -116,9 +116,9 @@ export function Sidebar() { {!hasProAccess && (
- +
)} @@ -126,7 +126,7 @@ export function Sidebar() {
- + Settings diff --git a/src/components/SlackFocusToggle.tsx b/src/components/SlackFocusToggle.tsx index 377124b5..0ad173fe 100644 --- a/src/components/SlackFocusToggle.tsx +++ b/src/components/SlackFocusToggle.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { SlackIcon } from '@/components/icons/SlackIcon' import { Switch } from '@/components/ui/switch' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Input } from '@/components/ui/input' import { Dialog, @@ -195,24 +195,26 @@ export function SlackFocusToggle({ slackSettings, onSlackSettingsChange }: Slack
- +
- +
diff --git a/src/components/SocialStatusSummary.tsx b/src/components/SocialStatusSummary.tsx index b802b5df..b8acd5e1 100644 --- a/src/components/SocialStatusSummary.tsx +++ b/src/components/SocialStatusSummary.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Skeleton } from '@/components/ui/skeleton' import { ConnectIcon } from '@/components/icons/ConnectIcon' import { useConnectedStore } from '@/lib/stores/connectedStore' @@ -82,7 +82,8 @@ const StatusButton = ()=> { if(!connected) { return (
- +
) diff --git a/src/components/TimeSelector.tsx b/src/components/TimeSelector.tsx index 4c4f16cb..66f41957 100644 --- a/src/components/TimeSelector.tsx +++ b/src/components/TimeSelector.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Check, ChevronsUpDown } from 'lucide-react' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Command, CommandEmpty, @@ -195,7 +195,8 @@ export function TimeSelector({ value: externalValue, onChange }: TimeSelectorPro return ( - + diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index bb791f31..bb4efc56 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { X } from 'lucide-react' import { Link, useNavigate } from 'react-router-dom' import { Logo } from '@/components/ui/logo' @@ -8,6 +8,7 @@ import { useShortcutStore } from '@/lib/stores/shortcutStore' import { CircleHelpIcon } from './icons/CircleHelpIcon' import { Tooltip, TooltipContent , TooltipTrigger } from './ui/tooltip' import { SocialStatusSummary } from './SocialStatusSummary' +import { NoAnalyticsButton } from './ui/no-analytics-button' interface TopNavProps { variant?: 'default' | 'modal' @@ -44,7 +45,8 @@ export function TopNav({ variant = 'default' }: TopNavProps) {
- + Feedback {variant === 'default' ? ( -
)} - + ) : ( - + )} diff --git a/src/components/TypewriterModeToggle.tsx b/src/components/TypewriterModeToggle.tsx deleted file mode 100644 index 02f91c15..00000000 --- a/src/components/TypewriterModeToggle.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useState } from 'react' -import { TypeOutline, KeyRound } from 'lucide-react' -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover' -import { PaywallDialog } from '@/components/PaywallDialog' -import { usePermissions } from '@/hooks/usePermissions' - -interface TypewriterModeToggleProps { - typewriterMode: boolean - onToggle: (value: boolean) => void -} - -export function TypewriterModeToggle({ typewriterMode, onToggle }: TypewriterModeToggleProps) { - const [isPopoverOpen, setIsPopoverOpen] = useState(false) - const { canUseTypewriter } = usePermissions() - - return ( -
- - -
setIsPopoverOpen(true)} - onMouseLeave={() => setIsPopoverOpen(false)} - > - {canUseTypewriter ? ( - - ) : ( - - - - )} -
-
- -
-

Typewriter Mode

- {canUseTypewriter ? ( - - {typewriterMode ? 'On' : 'Off'} - - ) : ( - - )} -
-

De-emphasize everything but the active window

-
-
-
- ) -} diff --git a/src/components/UsageSummary.tsx b/src/components/UsageSummary.tsx index 6b683c3a..a75b2fdf 100644 --- a/src/components/UsageSummary.tsx +++ b/src/components/UsageSummary.tsx @@ -16,7 +16,7 @@ import { import { Skeleton } from './ui/skeleton' import { GraphableTimeByHourBlock, AppsWithTime } from '@/api/monitorApi/monitorApi' import { AppIcon } from '@/components/AppIcon' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { useRef, useEffect, useState } from 'react' import { Switch } from '@/components/ui/switch' import { useCreateNotification, useGetNotificationBySentId } from '@/api/hooks/useNotifications' @@ -236,14 +236,15 @@ export const UsageSummary = ({
{sortedAppUsage.slice(0, 3).map((app, index) => ( - + ))}
diff --git a/src/components/UserProfileSettings.tsx b/src/components/UserProfileSettings.tsx index 77d6f3ba..2677da7a 100644 --- a/src/components/UserProfileSettings.tsx +++ b/src/components/UserProfileSettings.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { PaywallDialog } from '@/components/PaywallDialog' @@ -99,7 +99,8 @@ export function UserProfileSettings({ user }: UserProfileSettingsProps) {
- +
@@ -157,7 +158,13 @@ export function UserProfileSettings({ user }: UserProfileSettingsProps) { {license?.licenseType &&

Type: {license.licenseType}

} {license?.expirationDate &&

Updates until: {format(new Date(license.expirationDate), 'PP')}

} {license?.licenseType === 'subscription' && ( - + Manage Subscription )} @@ -181,10 +188,15 @@ export function UserProfileSettings({ user }: UserProfileSettingsProps) {
- +
diff --git a/src/components/developer/ResetAppData.tsx b/src/components/developer/ResetAppData.tsx index ac990ce8..0f4cf39b 100644 --- a/src/components/developer/ResetAppData.tsx +++ b/src/components/developer/ResetAppData.tsx @@ -1,9 +1,9 @@ import { useState } from 'react' -import { Button } from '@/components/ui/button' import { invoke } from '@tauri-apps/api/core' import { StorageUtils } from '@/lib/utils/storage.util' import { logAndToastError } from '@/lib/utils/ebbError.util' import { useAuth } from '../../hooks/useAuth' +import { NoAnalyticsButton } from '../ui/no-analytics-button' export const ResetAppData = () => { const [isResetting, setIsResetting] = useState(false) @@ -52,21 +52,21 @@ export const ResetAppData = () => { This will reset all app data to simulate a first-time experience. Your existing data will be backed up.

- + - +
) diff --git a/src/components/difficulty-selector/DifficultyButton.tsx b/src/components/difficulty-selector/DifficultyButton.tsx index 320e36af..3db1dda3 100644 --- a/src/components/difficulty-selector/DifficultyButton.tsx +++ b/src/components/difficulty-selector/DifficultyButton.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { cn } from '@/lib/utils/tailwind.util' import { KeyRound } from 'lucide-react' import { PaywallDialog } from '../PaywallDialog' @@ -23,7 +23,8 @@ export function DifficultyButton({ key={difficulty.value} className="relative" > - + {difficulty.value === 'hard' && isDisabled && (
- +
)} diff --git a/src/components/difficulty-selector/DifficultySelector.tsx b/src/components/difficulty-selector/DifficultySelector.tsx index 31c22e61..8b7e9af5 100644 --- a/src/components/difficulty-selector/DifficultySelector.tsx +++ b/src/components/difficulty-selector/DifficultySelector.tsx @@ -3,7 +3,7 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/popover' -import { Button } from '@/components/ui/button' +import { AnalyticsButton } from '@/components/ui/analytics-button' import { cn } from '@/lib/utils/tailwind.util' import { DifficultyButton } from './DifficultyButton' import { SignalBars } from './SignalBars' @@ -47,7 +47,8 @@ export function DifficultySelector({ value, onChange, className, disabledOptions return ( - + diff --git a/src/components/ui/no-analytics-button.tsx b/src/components/ui/no-analytics-button.tsx new file mode 100644 index 00000000..8e3f4c63 --- /dev/null +++ b/src/components/ui/no-analytics-button.tsx @@ -0,0 +1,19 @@ +import * as React from 'react' +import { Button, type ButtonProps } from './button' + + + +const NoAnalyticsButton = React.forwardRef( + ({ ...props }, ref) => { + + return ( +