mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(analytics): add Umami event tracking utilities
- Add comprehensive analytics.ts with type-safe event tracking - Include app-specific event helpers (Auth, Landing, Chat, Picture, Todo, Calendar, Clock, Contacts, ManaDeck, Subscription, App events) - Export from shared-utils package - Add complete documentation in docs/ANALYTICS.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
634bb97e83
commit
b8a84edfe0
3 changed files with 613 additions and 0 deletions
334
docs/ANALYTICS.md
Normal file
334
docs/ANALYTICS.md
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
# Analytics & Event Tracking
|
||||||
|
|
||||||
|
ManaCore verwendet Umami für Web Analytics. Alle Events werden zu `stats.mana.how` gesendet.
|
||||||
|
|
||||||
|
## Umami Dashboard
|
||||||
|
|
||||||
|
- **URL**: https://stats.mana.how
|
||||||
|
- **Public Stats**: Alle Websites haben Public Sharing aktiviert
|
||||||
|
|
||||||
|
## Website IDs
|
||||||
|
|
||||||
|
### Landing Pages
|
||||||
|
|
||||||
|
| App | Website ID | Public URL |
|
||||||
|
|-----|-----------|------------|
|
||||||
|
| Chat | `a264b165-80d2-47ab-91f4-2efc01de0b66` | stats.mana.how/share/chatlanding |
|
||||||
|
| ManaCore | `cef3798d-85ae-47df-a44a-e9bee09dbcf9` | stats.mana.how/share/manacorelanding |
|
||||||
|
| ManaDeck | `2ac83d50-107f-4d4e-ac23-5540946e96e3` | stats.mana.how/share/manadecklanding |
|
||||||
|
| Calendar | `84862d98-727e-4e25-8645-639241dd1544` | stats.mana.how/share/calendarlanding |
|
||||||
|
| Clock | `0332b471-a022-46af-a726-0f45932bfd58` | stats.mana.how/share/clocklanding |
|
||||||
|
| Picture | `d3ac98e6-0d1a-47a3-a218-2a81fff596bd` | stats.mana.how/share/picturelanding |
|
||||||
|
|
||||||
|
### Web Apps
|
||||||
|
|
||||||
|
| App | Website ID | Public URL |
|
||||||
|
|-----|-----------|------------|
|
||||||
|
| Chat | `5cf9d569-3266-4a57-80dd-3a652dc32786` | stats.mana.how/share/chatwebapp |
|
||||||
|
| ManaCore | `4a14016d-394a-44e0-8ecc-67271f63ffb0` | stats.mana.how/share/manacorewebapp |
|
||||||
|
| Todo | `ac021d98-778e-46cf-b6b2-2f650ea78f07` | stats.mana.how/share/todowebapp |
|
||||||
|
| Calendar | `884fc0a8-3b67-43bd-903b-2be531c66792` | stats.mana.how/share/calendarwebapp |
|
||||||
|
| Clock | `1e7b5006-87a5-4547-8a3d-ab30eac15dd4` | stats.mana.how/share/clockwebapp |
|
||||||
|
| Contacts | `ab89a839-be15-4949-99b4-e72492cee4ff` | stats.mana.how/share/contactswebapp |
|
||||||
|
| Picture | `bc552bd2-667d-44b4-a717-0dce6a8db98f` | stats.mana.how/share/picturewebapp |
|
||||||
|
| ManaDeck | `314fc57a-c63d-4008-b19e-5e272c0329d6` | stats.mana.how/share/manadeckwebapp |
|
||||||
|
| Planta | `876f30bd-43e3-405a-9697-6157db67ca6b` | stats.mana.how/share/plantawebapp |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom Event Tracking
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Die Analytics-Utilities sind in `@manacore/shared-utils` verfügbar:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
trackEvent,
|
||||||
|
trackClick,
|
||||||
|
AuthEvents,
|
||||||
|
LandingEvents,
|
||||||
|
ChatEvents,
|
||||||
|
// ...
|
||||||
|
} from '@manacore/shared-utils/analytics';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basis-Funktionen
|
||||||
|
|
||||||
|
#### `trackEvent(eventName, data?)`
|
||||||
|
|
||||||
|
Trackt ein benutzerdefiniertes Event.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackEvent('custom_action', { key: 'value' });
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `trackClick(elementId, label?)`
|
||||||
|
|
||||||
|
Trackt Button- oder Link-Klicks.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackClick('cta_hero', 'Get Started');
|
||||||
|
// Trackt: { event: 'click', element: 'cta_hero', label: 'Get Started' }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `trackView(section)`
|
||||||
|
|
||||||
|
Trackt Section/Page Views.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackView('pricing_section');
|
||||||
|
// Trackt: { event: 'view', section: 'pricing_section' }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `trackFormSubmit(formId, success)`
|
||||||
|
|
||||||
|
Trackt Formular-Submissions.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackFormSubmit('contact_form', true);
|
||||||
|
// Trackt: { event: 'form_submit', form: 'contact_form', success: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `trackSearch(query, resultsCount)`
|
||||||
|
|
||||||
|
Trackt Suchanfragen (nur Länge für Privacy).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackSearch('react hooks', 42);
|
||||||
|
// Trackt: { event: 'search', query_length: 11, results: 42 }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `trackError(errorType, message?)`
|
||||||
|
|
||||||
|
Trackt Fehler (Message wird auf 100 Zeichen gekürzt).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
trackError('api_error', 'Failed to fetch data');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## App-Spezifische Event Helpers
|
||||||
|
|
||||||
|
### AuthEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AuthEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
AuthEvents.login('email'); // login { method: 'email' }
|
||||||
|
AuthEvents.login('google'); // login { method: 'google' }
|
||||||
|
AuthEvents.logout(); // logout
|
||||||
|
AuthEvents.signup('email'); // signup { method: 'email' }
|
||||||
|
AuthEvents.signupCompleted(); // signup_completed
|
||||||
|
AuthEvents.passwordReset(); // password_reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### LandingEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LandingEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
LandingEvents.ctaClick('hero'); // cta_click { location: 'hero' }
|
||||||
|
LandingEvents.ctaClick('pricing'); // cta_click { location: 'pricing' }
|
||||||
|
LandingEvents.pricingViewed(); // pricing_viewed
|
||||||
|
LandingEvents.pricingPlanSelected('pro'); // pricing_plan_selected { plan: 'pro' }
|
||||||
|
LandingEvents.demoStarted(); // demo_started
|
||||||
|
LandingEvents.featureExplored('ai-chat'); // feature_explored { feature: 'ai-chat' }
|
||||||
|
LandingEvents.faqOpened('How does it work?'); // faq_opened { question: 'How does...' }
|
||||||
|
LandingEvents.contactFormSubmitted(); // contact_form_submitted
|
||||||
|
LandingEvents.newsletterSubscribed(); // newsletter_subscribed
|
||||||
|
```
|
||||||
|
|
||||||
|
### ChatEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ChatEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
ChatEvents.conversationCreated(); // conversation_created
|
||||||
|
ChatEvents.messageSent('gpt-4'); // message_sent { model: 'gpt-4' }
|
||||||
|
ChatEvents.modelChanged('claude-3'); // model_changed { model: 'claude-3' }
|
||||||
|
ChatEvents.conversationDeleted(); // conversation_deleted
|
||||||
|
ChatEvents.conversationShared(); // conversation_shared
|
||||||
|
```
|
||||||
|
|
||||||
|
### PictureEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PictureEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
PictureEvents.imageGenerated('flux', 'realistic'); // image_generated { model: 'flux', style: 'realistic' }
|
||||||
|
PictureEvents.imageDownloaded(); // image_downloaded
|
||||||
|
PictureEvents.imageFavorited(); // image_favorited
|
||||||
|
PictureEvents.imageShared(); // image_shared
|
||||||
|
PictureEvents.modelSelected('sdxl'); // model_selected { model: 'sdxl' }
|
||||||
|
PictureEvents.styleSelected('anime'); // style_selected { style: 'anime' }
|
||||||
|
PictureEvents.generationFailed('timeout'); // generation_failed { reason: 'timeout' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### TodoEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TodoEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
TodoEvents.taskCreated(true); // task_created { has_deadline: true }
|
||||||
|
TodoEvents.taskCompleted(); // task_completed
|
||||||
|
TodoEvents.taskDeleted(); // task_deleted
|
||||||
|
TodoEvents.projectCreated(); // project_created
|
||||||
|
TodoEvents.labelCreated(); // label_created
|
||||||
|
TodoEvents.viewChanged('today'); // view_changed { view: 'today' }
|
||||||
|
```
|
||||||
|
|
||||||
|
### CalendarEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { CalendarEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
CalendarEvents.eventCreated(true); // event_created { recurring: true }
|
||||||
|
CalendarEvents.eventUpdated(); // event_updated
|
||||||
|
CalendarEvents.eventDeleted(); // event_deleted
|
||||||
|
CalendarEvents.calendarCreated(); // calendar_created
|
||||||
|
CalendarEvents.calendarShared(); // calendar_shared
|
||||||
|
CalendarEvents.viewChanged('week'); // view_changed { view: 'week' }
|
||||||
|
CalendarEvents.reminderSet(30); // reminder_set { minutes: 30 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### ClockEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ClockEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
ClockEvents.timerStarted('pomodoro'); // timer_started { type: 'pomodoro' }
|
||||||
|
ClockEvents.timerCompleted('pomodoro', 1500); // timer_completed { type: 'pomodoro', duration_seconds: 1500 }
|
||||||
|
ClockEvents.timerCanceled(); // timer_canceled
|
||||||
|
ClockEvents.focusSessionStarted(); // focus_session_started
|
||||||
|
ClockEvents.focusSessionCompleted(45); // focus_session_completed { duration_minutes: 45 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### ContactsEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ContactsEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
ContactsEvents.contactCreated(); // contact_created
|
||||||
|
ContactsEvents.contactUpdated(); // contact_updated
|
||||||
|
ContactsEvents.contactDeleted(); // contact_deleted
|
||||||
|
ContactsEvents.contactImported('google'); // contact_imported { source: 'google' }
|
||||||
|
ContactsEvents.contactExported('vcard'); // contact_exported { format: 'vcard' }
|
||||||
|
ContactsEvents.tagCreated(); // tag_created
|
||||||
|
ContactsEvents.searchPerformed(); // search_performed
|
||||||
|
```
|
||||||
|
|
||||||
|
### ManaDeckEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ManaDeckEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
ManaDeckEvents.deckCreated(); // deck_created
|
||||||
|
ManaDeckEvents.deckStudied(25); // deck_studied { cards: 25 }
|
||||||
|
ManaDeckEvents.cardCreated(); // card_created
|
||||||
|
ManaDeckEvents.cardReviewed(4); // card_reviewed { rating: 4 }
|
||||||
|
ManaDeckEvents.aiCardsGenerated(10); // ai_cards_generated { count: 10 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### SubscriptionEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SubscriptionEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
SubscriptionEvents.pricingViewed(); // pricing_viewed
|
||||||
|
SubscriptionEvents.planSelected('pro'); // plan_selected { plan: 'pro' }
|
||||||
|
SubscriptionEvents.checkoutStarted('pro'); // checkout_started { plan: 'pro' }
|
||||||
|
SubscriptionEvents.checkoutCompleted('pro'); // checkout_completed { plan: 'pro' }
|
||||||
|
SubscriptionEvents.checkoutAbandoned('pro'); // checkout_abandoned { plan: 'pro' }
|
||||||
|
SubscriptionEvents.subscriptionCanceled('pro'); // subscription_canceled { plan: 'pro' }
|
||||||
|
SubscriptionEvents.trialStarted(); // trial_started
|
||||||
|
SubscriptionEvents.trialEnded(true); // trial_ended { converted: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
### AppEvents
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AppEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
AppEvents.appOpened('chat'); // app_opened { app: 'chat' }
|
||||||
|
AppEvents.themeChanged('dark'); // theme_changed { theme: 'dark' }
|
||||||
|
AppEvents.languageChanged('de'); // language_changed { language: 'de' }
|
||||||
|
AppEvents.feedbackSubmitted('bug'); // feedback_submitted { type: 'bug' }
|
||||||
|
AppEvents.helpOpened(); // help_opened
|
||||||
|
AppEvents.settingsOpened(); // settings_opened
|
||||||
|
AppEvents.shareClicked('twitter'); // share_clicked { platform: 'twitter' }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Guide
|
||||||
|
|
||||||
|
### Svelte/SvelteKit
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
import { LandingEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
function handleCtaClick() {
|
||||||
|
LandingEvents.ctaClick('hero');
|
||||||
|
// Navigate to app...
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={handleCtaClick}>
|
||||||
|
Get Started
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Astro Landing Pages
|
||||||
|
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
// Layout.astro - Script tag is already in <head>
|
||||||
|
---
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { LandingEvents } from '@manacore/shared-utils/analytics';
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-cta]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const location = btn.getAttribute('data-cta');
|
||||||
|
LandingEvents.ctaClick(location);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Mode
|
||||||
|
|
||||||
|
Im Development-Modus (`import.meta.env?.DEV`) werden Events in die Console geloggt:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Analytics] cta_click { location: 'hero' }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Event Naming Conventions
|
||||||
|
|
||||||
|
1. **snake_case** für Event-Namen: `task_created`, nicht `taskCreated`
|
||||||
|
2. **Kurze, beschreibende Namen**: `signup_completed`, nicht `user_has_completed_signup_process`
|
||||||
|
3. **Konsistente Suffixe**:
|
||||||
|
- `_created`, `_updated`, `_deleted` für CRUD
|
||||||
|
- `_started`, `_completed`, `_canceled` für Prozesse
|
||||||
|
- `_clicked`, `_viewed` für UI-Interaktionen
|
||||||
|
|
||||||
|
## Privacy
|
||||||
|
|
||||||
|
- Keine persönlichen Daten in Events (keine E-Mails, Namen, etc.)
|
||||||
|
- Suchanfragen: Nur Länge wird getracked, nicht der Inhalt
|
||||||
|
- Error Messages: Auf 100 Zeichen gekürzt
|
||||||
|
- GDPR-konform: Umami ist privacy-focused und setzt keine Cookies
|
||||||
|
|
||||||
|
## Umami Server
|
||||||
|
|
||||||
|
- **Host**: Mac Mini (mana-server)
|
||||||
|
- **Container**: `umami`
|
||||||
|
- **Datenbank**: PostgreSQL (shared mit anderen Services)
|
||||||
|
- **Port**: 3200 (intern), via Caddy erreichbar unter stats.mana.how
|
||||||
276
packages/shared-utils/src/analytics.ts
Normal file
276
packages/shared-utils/src/analytics.ts
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
/**
|
||||||
|
* Umami Analytics Utility
|
||||||
|
*
|
||||||
|
* Provides type-safe event tracking for all ManaCore apps.
|
||||||
|
* Events are automatically sent to Umami at stats.mana.how
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { trackEvent, trackClick } from '@manacore/shared-utils/analytics';
|
||||||
|
*
|
||||||
|
* // Track a custom event
|
||||||
|
* trackEvent('signup_completed', { method: 'email' });
|
||||||
|
*
|
||||||
|
* // Track a button click
|
||||||
|
* trackClick('cta_hero', 'Get Started');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Umami types
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
umami?: {
|
||||||
|
track: (eventName: string, eventData?: Record<string, string | number | boolean>) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Umami is available
|
||||||
|
*/
|
||||||
|
export function isUmamiAvailable(): boolean {
|
||||||
|
return typeof window !== 'undefined' && typeof window.umami?.track === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track a custom event
|
||||||
|
*
|
||||||
|
* @param eventName - Name of the event (snake_case recommended)
|
||||||
|
* @param data - Optional event data/properties
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackEvent('image_generated', { model: 'flux', style: 'realistic' });
|
||||||
|
*/
|
||||||
|
export function trackEvent(
|
||||||
|
eventName: string,
|
||||||
|
data?: Record<string, string | number | boolean>
|
||||||
|
): void {
|
||||||
|
if (!isUmamiAvailable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.umami!.track(eventName, data);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Analytics] Failed to track event:', eventName, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track a button/link click
|
||||||
|
*
|
||||||
|
* @param elementId - Identifier for the element (e.g., 'cta_hero', 'nav_pricing')
|
||||||
|
* @param label - Human-readable label
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackClick('cta_hero', 'Start Free Trial');
|
||||||
|
*/
|
||||||
|
export function trackClick(elementId: string, label?: string): void {
|
||||||
|
trackEvent('click', { element: elementId, label: label || elementId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track a page/section view
|
||||||
|
*
|
||||||
|
* @param section - Section identifier
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackView('pricing_section');
|
||||||
|
*/
|
||||||
|
export function trackView(section: string): void {
|
||||||
|
trackEvent('view', { section });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track form submission
|
||||||
|
*
|
||||||
|
* @param formId - Form identifier
|
||||||
|
* @param success - Whether submission was successful
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackFormSubmit('contact_form', true);
|
||||||
|
*/
|
||||||
|
export function trackFormSubmit(formId: string, success: boolean): void {
|
||||||
|
trackEvent('form_submit', { form: formId, success });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track search queries
|
||||||
|
*
|
||||||
|
* @param query - Search query (consider privacy - don't track full queries)
|
||||||
|
* @param resultsCount - Number of results
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackSearch('react hooks', 42);
|
||||||
|
*/
|
||||||
|
export function trackSearch(query: string, resultsCount: number): void {
|
||||||
|
// Only track query length for privacy
|
||||||
|
trackEvent('search', { query_length: query.length, results: resultsCount });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track errors
|
||||||
|
*
|
||||||
|
* @param errorType - Type of error
|
||||||
|
* @param message - Error message (sanitized)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* trackError('api_error', 'Failed to fetch data');
|
||||||
|
*/
|
||||||
|
export function trackError(errorType: string, message?: string): void {
|
||||||
|
trackEvent('error', {
|
||||||
|
type: errorType,
|
||||||
|
message: message?.substring(0, 100) || 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// App-Specific Event Helpers
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth Events
|
||||||
|
*/
|
||||||
|
export const AuthEvents = {
|
||||||
|
login: (method: 'email' | 'google' | 'github' = 'email') => trackEvent('login', { method }),
|
||||||
|
logout: () => trackEvent('logout'),
|
||||||
|
signup: (method: 'email' | 'google' | 'github' = 'email') => trackEvent('signup', { method }),
|
||||||
|
signupCompleted: () => trackEvent('signup_completed'),
|
||||||
|
passwordReset: () => trackEvent('password_reset'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Landing Page Events
|
||||||
|
*/
|
||||||
|
export const LandingEvents = {
|
||||||
|
ctaClick: (location: 'hero' | 'pricing' | 'features' | 'footer' | string) =>
|
||||||
|
trackEvent('cta_click', { location }),
|
||||||
|
pricingViewed: () => trackEvent('pricing_viewed'),
|
||||||
|
pricingPlanSelected: (plan: string) => trackEvent('pricing_plan_selected', { plan }),
|
||||||
|
demoStarted: () => trackEvent('demo_started'),
|
||||||
|
featureExplored: (feature: string) => trackEvent('feature_explored', { feature }),
|
||||||
|
faqOpened: (question: string) =>
|
||||||
|
trackEvent('faq_opened', { question: question.substring(0, 50) }),
|
||||||
|
contactFormSubmitted: () => trackEvent('contact_form_submitted'),
|
||||||
|
newsletterSubscribed: () => trackEvent('newsletter_subscribed'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat App Events
|
||||||
|
*/
|
||||||
|
export const ChatEvents = {
|
||||||
|
conversationCreated: () => trackEvent('conversation_created'),
|
||||||
|
messageSent: (modelId?: string) =>
|
||||||
|
trackEvent('message_sent', modelId ? { model: modelId } : undefined),
|
||||||
|
modelChanged: (modelId: string) => trackEvent('model_changed', { model: modelId }),
|
||||||
|
conversationDeleted: () => trackEvent('conversation_deleted'),
|
||||||
|
conversationShared: () => trackEvent('conversation_shared'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picture App Events
|
||||||
|
*/
|
||||||
|
export const PictureEvents = {
|
||||||
|
imageGenerated: (model: string, style?: string) =>
|
||||||
|
trackEvent('image_generated', { model, ...(style && { style }) }),
|
||||||
|
imageDownloaded: () => trackEvent('image_downloaded'),
|
||||||
|
imageFavorited: () => trackEvent('image_favorited'),
|
||||||
|
imageShared: () => trackEvent('image_shared'),
|
||||||
|
modelSelected: (model: string) => trackEvent('model_selected', { model }),
|
||||||
|
styleSelected: (style: string) => trackEvent('style_selected', { style }),
|
||||||
|
generationFailed: (reason?: string) =>
|
||||||
|
trackEvent('generation_failed', { reason: reason || 'unknown' }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo App Events
|
||||||
|
*/
|
||||||
|
export const TodoEvents = {
|
||||||
|
taskCreated: (hasDeadline = false) => trackEvent('task_created', { has_deadline: hasDeadline }),
|
||||||
|
taskCompleted: () => trackEvent('task_completed'),
|
||||||
|
taskDeleted: () => trackEvent('task_deleted'),
|
||||||
|
projectCreated: () => trackEvent('project_created'),
|
||||||
|
labelCreated: () => trackEvent('label_created'),
|
||||||
|
viewChanged: (view: 'inbox' | 'today' | 'upcoming' | 'project') =>
|
||||||
|
trackEvent('view_changed', { view }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar App Events
|
||||||
|
*/
|
||||||
|
export const CalendarEvents = {
|
||||||
|
eventCreated: (isRecurring = false) => trackEvent('event_created', { recurring: isRecurring }),
|
||||||
|
eventUpdated: () => trackEvent('event_updated'),
|
||||||
|
eventDeleted: () => trackEvent('event_deleted'),
|
||||||
|
calendarCreated: () => trackEvent('calendar_created'),
|
||||||
|
calendarShared: () => trackEvent('calendar_shared'),
|
||||||
|
viewChanged: (view: 'day' | 'week' | 'month' | 'agenda') => trackEvent('view_changed', { view }),
|
||||||
|
reminderSet: (minutesBefore: number) => trackEvent('reminder_set', { minutes: minutesBefore }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clock App Events
|
||||||
|
*/
|
||||||
|
export const ClockEvents = {
|
||||||
|
timerStarted: (type: 'pomodoro' | 'stopwatch' | 'countdown') =>
|
||||||
|
trackEvent('timer_started', { type }),
|
||||||
|
timerCompleted: (type: 'pomodoro' | 'stopwatch' | 'countdown', duration: number) =>
|
||||||
|
trackEvent('timer_completed', { type, duration_seconds: duration }),
|
||||||
|
timerCanceled: () => trackEvent('timer_canceled'),
|
||||||
|
focusSessionStarted: () => trackEvent('focus_session_started'),
|
||||||
|
focusSessionCompleted: (duration: number) =>
|
||||||
|
trackEvent('focus_session_completed', { duration_minutes: duration }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contacts App Events
|
||||||
|
*/
|
||||||
|
export const ContactsEvents = {
|
||||||
|
contactCreated: () => trackEvent('contact_created'),
|
||||||
|
contactUpdated: () => trackEvent('contact_updated'),
|
||||||
|
contactDeleted: () => trackEvent('contact_deleted'),
|
||||||
|
contactImported: (source: 'google' | 'csv' | 'vcard') =>
|
||||||
|
trackEvent('contact_imported', { source }),
|
||||||
|
contactExported: (format: 'csv' | 'vcard') => trackEvent('contact_exported', { format }),
|
||||||
|
tagCreated: () => trackEvent('tag_created'),
|
||||||
|
searchPerformed: () => trackEvent('search_performed'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ManaDeck App Events
|
||||||
|
*/
|
||||||
|
export const ManaDeckEvents = {
|
||||||
|
deckCreated: () => trackEvent('deck_created'),
|
||||||
|
deckStudied: (cardsCount: number) => trackEvent('deck_studied', { cards: cardsCount }),
|
||||||
|
cardCreated: () => trackEvent('card_created'),
|
||||||
|
cardReviewed: (rating: 1 | 2 | 3 | 4 | 5) => trackEvent('card_reviewed', { rating }),
|
||||||
|
aiCardsGenerated: (count: number) => trackEvent('ai_cards_generated', { count }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription/Payment Events
|
||||||
|
*/
|
||||||
|
export const SubscriptionEvents = {
|
||||||
|
pricingViewed: () => trackEvent('pricing_viewed'),
|
||||||
|
planSelected: (plan: string) => trackEvent('plan_selected', { plan }),
|
||||||
|
checkoutStarted: (plan: string) => trackEvent('checkout_started', { plan }),
|
||||||
|
checkoutCompleted: (plan: string) => trackEvent('checkout_completed', { plan }),
|
||||||
|
checkoutAbandoned: (plan: string) => trackEvent('checkout_abandoned', { plan }),
|
||||||
|
subscriptionCanceled: (plan: string) => trackEvent('subscription_canceled', { plan }),
|
||||||
|
trialStarted: () => trackEvent('trial_started'),
|
||||||
|
trialEnded: (converted: boolean) => trackEvent('trial_ended', { converted }),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General App Events
|
||||||
|
*/
|
||||||
|
export const AppEvents = {
|
||||||
|
appOpened: (app: string) => trackEvent('app_opened', { app }),
|
||||||
|
themeChanged: (theme: 'light' | 'dark' | 'system') => trackEvent('theme_changed', { theme }),
|
||||||
|
languageChanged: (language: string) => trackEvent('language_changed', { language }),
|
||||||
|
feedbackSubmitted: (type: 'bug' | 'feature' | 'other') =>
|
||||||
|
trackEvent('feedback_submitted', { type }),
|
||||||
|
helpOpened: () => trackEvent('help_opened'),
|
||||||
|
settingsOpened: () => trackEvent('settings_opened'),
|
||||||
|
shareClicked: (platform: string) => trackEvent('share_clicked', { platform }),
|
||||||
|
};
|
||||||
|
|
@ -25,3 +25,6 @@ export * from './cache';
|
||||||
|
|
||||||
// Natural Language Parsers
|
// Natural Language Parsers
|
||||||
export * from './parsers';
|
export * from './parsers';
|
||||||
|
|
||||||
|
// Umami Analytics
|
||||||
|
export * from './analytics';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue