diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 804538a3..b41e6b5d 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}", "src/components/**/*.{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/api/hooks/useKanbanBoard.ts b/src/api/hooks/useKanbanBoard.ts
index 94cb9310..e40474be 100644
--- a/src/api/hooks/useKanbanBoard.ts
+++ b/src/api/hooks/useKanbanBoard.ts
@@ -4,6 +4,7 @@ import { DragEndEvent } from '@dnd-kit/core'
import { AppsWithTime } from '@/api/monitorApi/monitorApi' // Adjust path as needed for types
import { ActivityRating } from '@/lib/app-directory/apps-types' // Adjust path as needed for types
import { useAppUsage, useTags, useUpdateAppRatingMutation } from '@/api/hooks/useAppUsage'
+import { AnalyticsService } from '@/lib/analytics'
// --- Type Definitions for Kanban Board ---
@@ -101,6 +102,7 @@ export const useKanbanBoard = ({ rangeMode, date }: { rangeMode: 'day' | 'week'
updatedColumns[targetColumnId].push(app)
updatedColumns[targetColumnId].sort((a, b) => b.duration - a.duration)
}
+ AnalyticsService.trackEvent('app_dragged')
return updatedColumns
})
updateAppRatingMutation.mutate({ appTagId, rating: rating as ActivityRating })
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}
/>
- }>
+ }
+ >
Invite
-
+
@@ -358,10 +369,16 @@ export const FriendsComparisonCard = ({
}
-
+
@@ -429,17 +446,23 @@ export const FriendsComparisonCard = ({
/>
-
-
}>
+
+
}
+ >
Send Invite
-
+
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 (
-
{selectedOption.label}
-
+
{rangeOptions.map((option) => (
- onChange(option.value)}
+ analyticsEvent={'range_mode_clicked'}
>
{option.label}
-
+
))}
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
-
Daily
-
-
+
Weekly
-
+
{recurrenceType === 'weekly' && (
@@ -180,7 +181,7 @@ export function ScheduleSessionModal({ scheduleId, onClose }: ScheduleSessionMod
{dayNames.map((day, index) => (
- handleDayToggle(index)}
>
{day}
-
+
))}
@@ -207,19 +208,20 @@ export function ScheduleSessionModal({ scheduleId, onClose }: ScheduleSessionMod
-
Cancel
-
-
+
{isSubmitting ? 'Saving...' : scheduleId ? 'Update Schedule' : 'Create Schedule'}
-
+
)
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 (
-
@@ -185,7 +186,7 @@ export function ShortcutInput({ popoverAlign = 'center' }: ShortcutInputProps) {
) : (
Click to set
)}
-
+
{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 */}
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -38,7 +38,7 @@ export function Sidebar() {
-
+
Today
@@ -46,7 +46,7 @@ export function Sidebar() {
{isFocusScheduleFeatureEnabled(user?.email) && (
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -55,14 +55,14 @@ export function Sidebar() {
-
+
Focus Schedule
)}
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -71,14 +71,14 @@ export function Sidebar() {
-
+
Categories
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -90,7 +90,7 @@ export function Sidebar() {
)}
-
+
Friends
@@ -98,7 +98,7 @@ export function Sidebar() {
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -107,7 +107,7 @@ export function Sidebar() {
-
+
Community
@@ -116,9 +116,9 @@ export function Sidebar() {
{!hasProAccess && (
)}
@@ -126,7 +126,7 @@ export function Sidebar() {
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -135,7 +135,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
-
{updatePreferencesMutation.isPending ? 'Saving...' : 'Save Preferences'}
-
+
-
More Slack Settings
-
+
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 (
-
{
>
{isOffline ? 'Offline' : 'Connect '}
-
+
)
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) {
- svg]:text-foreground' : 'text-muted-foreground [&>svg]:text-muted-foreground'}`}
@@ -53,12 +55,13 @@ export function TopNav({ variant = 'default' }: TopNavProps) {
-
+
Feedback
{variant === 'default' ? (
-
@@ -70,11 +73,15 @@ export function TopNav({ variant = 'default' }: TopNavProps) {
))}
)}
-
+
) : (
- navigate('/')}>
+ navigate('/')}
+ >
-
+
)}
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 ? (
-
{
- e.preventDefault()
- onToggle(!typewriterMode)
- }}
- >
-
-
- ) : (
-
-
-
-
-
- )}
-
-
-
-
-
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) {
-
Login with Google
-
+
@@ -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
+ 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.
-
{isResetting ? 'Resetting...' : 'Reset App Data'}
-
+
-
{isRestoring ? 'Restoring...' : 'Restore Last Backup'}
-
+
)
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.description}
-
+
{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 (
- e.stopPropagation()}
>
-
+
(
+ ({ analyticsEvent, analyticsProperties, onClick, ...props }, ref) => {
+ const handleClick = (e: React.MouseEvent) => {
+ console.log('AnalyticsButton clicked:', { analyticsEvent, analyticsProperties })
+ // Track the analytics event
+ AnalyticsService.trackEvent(analyticsEvent, analyticsProperties)
+
+ // Call the original onClick handler
+ if (onClick) {
+ onClick(e)
+ }
+ }
+
+ return (
+
+ )
+ },
+)
+AnalyticsButton.displayName = 'AnalyticsButton'
+
+export { AnalyticsButton }
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index c562e817..113f2ae7 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
import { DayPicker } from 'react-day-picker'
import { cn } from '@/lib/utils/tailwind.util'
+// eslint-disable-next-line
import { buttonVariants } from '@/components/ui/button'
export type CalendarProps = React.ComponentProps
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 (
+
+ )
+ },
+)
+NoAnalyticsButton.displayName = 'NoAnalyticsButton'
+
+export { NoAnalyticsButton }
diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts
new file mode 100644
index 00000000..db5d48df
--- /dev/null
+++ b/src/lib/analytics.ts
@@ -0,0 +1,167 @@
+import { default as posthog } from 'posthog-js'
+import { NotificationType } from '@/components/NotificationPanel/NotificationPanel'
+
+export type AnalyticsEvent =
+ // Focus Session Events
+ | 'start_focus_clicked'
+ | 'end_early_clicked'
+ | 'focus_session_completed'
+ | 'add_time_clicked'
+ | 'allow_list_clicked'
+ | 'block_list_clicked'
+ | 'focus_session_duration_selector_clicked'
+
+ // Schedule Management Events
+ | 'create_schedule_clicked'
+ | 'edit_schedule_clicked'
+ | 'delete_schedule_clicked'
+
+ // Navigation Events
+ | 'sidebar_nav_clicked'
+ | 'top_nav_help_clicked'
+
+ // Workflow Events
+ | 'workflow_selected'
+ | 'workflow_edited'
+ | 'workflow_deleted'
+
+ // Media Control Events
+ | 'music_play_clicked'
+ | 'music_pause_clicked'
+ | 'music_next_clicked'
+ | 'music_previous_clicked'
+
+ // Upgrade/Pro Events
+ | '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'
+ | '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'
+
+ // Customize Shortcut Events
+ | 'shortcut_input_clicked'
+
+ // Slack Focus Toggle Events
+ | 'slack_focus_settings_save_clicked'
+ | 'slack_focus_settings_more_settings_clicked'
+
+ // Social Status Summary Events
+ | 'connect_to_friends_clicked'
+
+ // Top Nav Events
+ | 'top_nav_feedback_clicked'
+ | 'top_nav_start_focus_clicked'
+
+ // Usage Summary Events
+ | 'usage_summary_top_apps_clicked'
+
+ // User Profile Settings Events
+ | 'user_profile_settings_login_with_google_clicked'
+ | 'user_profile_settings_manage_subscription_clicked'
+ | 'user_profile_settings_logout_clicked'
+
+ // Paywall Dialog Events
+ | 'paywall_dialog_hard_clicked'
+
+ // Difficulty Selector Events
+ | 'difficulty_selector_clicked'
+
+ // App Kanban Board Events
+ | 'app_dragged'
+
+export interface AnalyticsEventProperties {
+ // Focus session properties
+ difficulty?: 'easy' | 'medium' | 'hard'
+ session_duration?: number
+ workflow_id?: string
+ workflow_name?: string
+
+ // Navigation properties
+ destination?: string
+ source?: string
+
+ // Schedule properties
+ schedule_type?: 'one_time' | 'recurring'
+ recurrence_type?: 'daily' | 'weekly' | 'monthly'
+
+ // General properties
+ button_location?: string
+ keyboard_shortcut_used?: boolean
+ context?: string
+}
+
+const trackEvent = (
+ event: AnalyticsEvent,
+ properties?: AnalyticsEventProperties
+): void => {
+ console.log('Tracking event:', event, properties)
+ try {
+ posthog.capture(event, {
+ ...properties,
+ timestamp: new Date().toISOString(),
+ app_version: '0.4.7',
+ })
+ } catch (error) {
+ console.warn('Failed to track analytics event:', event, error)
+ }
+}
+
+export const AnalyticsService = {
+ trackEvent,
+}
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 === 'granted' ? 'Continue' : 'Enable Permissions'}
-
+
{(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 () {
-
{date.toLocaleDateString() === new Date().toLocaleDateString()
@@ -225,7 +230,7 @@ export default function CategoryDashboardPage () {
: DateTime.fromJSDate(date).toFormat('LLL dd, yyyy')}
-
+
- upgrade to Ebb Pro
+
+ 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' }}
/>
-
+
{submitting ? 'Submitting...' : 'Submit Feedback'}
-
+
)}
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 = () => {
-
-
-
+
{isPlaying ? (
) : (
)}
-
-
+
-
+
)
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 && (
-
{isAddingTime ? (
<>
@@ -112,7 +117,7 @@ export const Timer = ({ flowSession }: { flowSession: FlowSession | null }) => {
) : (
'Add 15 min'
)}
-
+
)}
>
)
diff --git a/src/pages/FocusSchedulePage.tsx b/src/pages/FocusSchedulePage.tsx
index 4120dee0..299dfbeb 100644
--- a/src/pages/FocusSchedulePage.tsx
+++ b/src/pages/FocusSchedulePage.tsx
@@ -1,6 +1,6 @@
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 +76,18 @@ export default function FocusSchedulePage() {
Schedule focus sessions to protect your most productive times
-
+
Create Schedule
-
+
)}
@@ -104,20 +112,30 @@ export default function FocusSchedulePage() {
-
handleEditSchedule(schedule.id)}
+ analyticsEvent="edit_schedule_clicked"
+ analyticsProperties={{
+ schedule_type: schedule.recurrence?.type ? 'recurring' : 'one_time',
+ button_location: 'focus_schedule_page'
+ }}
>
Edit
-
-
+ handleDeleteSchedule(schedule.id)}
+ analyticsEvent="delete_schedule_clicked"
+ analyticsProperties={{
+ schedule_type: schedule.recurrence?.type ? 'recurring' : 'one_time',
+ button_location: 'focus_schedule_page'
+ }}
>
-
+
@@ -130,10 +148,16 @@ export default function FocusSchedulePage() {
Create your first focus schedule to start protecting your productive time.
-
+
Create Your First Schedule
-
+
)}
@@ -164,8 +188,28 @@ export default function FocusSchedulePage() {
- setShowDeleteDialog(false)}>Cancel
- Delete
+ setShowDeleteDialog(false)}
+ analyticsEvent="delete_schedule_clicked"
+ analyticsProperties={{
+ button_location: 'delete_dialog',
+ context: 'cancel'
+ }}
+ >
+ Cancel
+
+
+ Delete
+
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.
-
Offline
-
+
>
) : (
<>
@@ -365,15 +370,20 @@ export const FriendsAnalyticsPreview = () => {
Connect with the community to unlock detailed analytics, compare with friends, and track your progress!
-
{isUpdatingProfileLocation ? 'Connecting...' : 'Connect'}
-
+
>
)}
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 = () => {
-
{date.toLocaleDateString() === new Date().toLocaleDateString()
@@ -65,7 +70,7 @@ export const HomePage = () => {
: DateTime.fromJSDate(date).toFormat('LLL dd, yyyy')}
-
+
{
Focus starts here.
{error && {error}
}
-
Continue with Google
-
+
-
Do this later
-
+
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' ? (
-
handleUnlink('spotify')}
+ analyticsEvent="spotify_disconnect_clicked"
+ analyticsProperties={{
+ context: 'spotify_disconnect',
+ button_location: 'integration_settings'
+ }}
>
Disconnect
-
+
) : (
-
Connect
-
+
{activeService === 'apple' && (
@@ -165,8 +175,28 @@ export const IntegrationSettings = () => {
- setShowUnlinkDialog(false)}>Cancel
- Disconnect
+ 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 ? (
-
Configure
-
+
) : (
-
Connect
-
+
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() {
- setShowDeleteAccountDialog(true)}
+ analyticsEvent="delete_account_clicked"
+ analyticsProperties={{ button_location: 'settings_page' }}
>
Delete Account
-
+
@@ -255,20 +257,24 @@ export function SettingsPage() {
- setShowDeleteAccountDialog(false)}
disabled={isDeleting}
+ analyticsEvent="delete_account_clicked_canceled"
+ analyticsProperties={{ button_location: 'settings_page' }}
>
Cancel
-
-
+
{isDeleting ? 'Deleting...' : 'Delete Account'}
-
+
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 = () => {
-
Continue
-
+
)
}
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 ? 'Login Required' : 'Connect Slack'}
-
+
-
Skip for Now
-
+
{!user && (
diff --git a/src/pages/StartFlowPage/StartFlowPage.tsx b/src/pages/StartFlowPage/StartFlowPage.tsx
index 4166fd2f..a66e8d7e 100644
--- a/src/pages/StartFlowPage/StartFlowPage.tsx
+++ b/src/pages/StartFlowPage/StartFlowPage.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { motion } from 'framer-motion'
-import { Button } from '@/components/ui/button'
+import { AnalyticsButton } from '@/components/ui/analytics-button'
import { Card, CardContent } from '@/components/ui/card'
import { TopNav } from '@/components/TopNav'
import { TimeSelector } from '@/components/TimeSelector'
@@ -17,6 +17,7 @@ import { logAndToastError } from '@/lib/utils/ebbError.util'
import { error as logError } from '@tauri-apps/plugin-log'
import { BlockingPreferenceApi } from '@/api/ebbApi/blockingPreferenceApi'
import { usePostHog } from 'posthog-js/react'
+import { AnalyticsService } from '@/lib/analytics'
import { Input } from '@/components/ui/input'
import { FlowSessionApi } from '@/api/ebbApi/flowSessionApi'
import { SmartFocusSelector } from '@/pages/StartFlowPage/SmartFocusSelector'
@@ -274,13 +275,20 @@ export const StartFlowPage = () => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault()
+ // Track keyboard shortcut usage
+ AnalyticsService.trackEvent('start_focus_clicked', {
+ workflow_id: selectedWorkflowId || undefined,
+ workflow_name: selectedWorkflow?.name,
+ button_location: 'start_flow_page',
+ keyboard_shortcut_used: true,
+ })
handleBegin()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
- }, [handleBegin])
+ }, [handleBegin, selectedWorkflowId, selectedWorkflow?.name])
return (
@@ -373,16 +381,23 @@ export const StartFlowPage = () => {
-
Start Focus
⌘
↵
-
+