Skip to content

Commit 3c8bfd9

Browse files
Merge branch 'main' of github.com:journium/react-vite-example
2 parents 63b3f97 + 82c6807 commit 3c8bfd9

14 files changed

Lines changed: 530 additions & 121 deletions

File tree

src/components/CelebrationDialog.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Dialog, DialogContent } from "@/components/ui/dialog";
22
import { Button } from "@/components/ui/button";
33
import { useNavigate } from "react-router-dom";
4+
import { useEffect } from "react";
5+
import { track, EVENTS } from "@/lib/events";
46
import { Sparkles, TrendingUp, PartyPopper } from "lucide-react";
57

68
interface CelebrationDialogProps {
@@ -14,7 +16,19 @@ export function CelebrationDialog({ open, onOpenChange, completedCount, totalCou
1416
const navigate = useNavigate();
1517
const isFullCompletion = completedCount === totalCount;
1618

19+
// Track dialog shown
20+
useEffect(() => {
21+
if (open) {
22+
track(EVENTS.DAY_COMPLETION_DIALOG_SHOWN, {
23+
completedCount,
24+
totalCount,
25+
isFullCompletion
26+
});
27+
}
28+
}, [open, completedCount, totalCount, isFullCompletion]);
29+
1730
const handleViewInsights = () => {
31+
track(EVENTS.DAY_COMPLETION_VIEW_INSIGHTS_CLICKED);
1832
onOpenChange(false);
1933
navigate("/insights");
2034
};

src/components/PaywallDialog.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,45 @@ import { Button } from "@/components/ui/button";
33
import { Badge } from "@/components/ui/badge";
44
import { Check, X, Sparkles, Zap } from "lucide-react";
55
import { useApp } from "@/lib/store";
6+
import { useEffect } from "react";
67
import { toast } from "sonner";
7-
import { track, Events } from "@/lib/events";
8+
import { track, EVENTS } from "@/lib/events";
89

910
interface PaywallDialogProps {
1011
open: boolean;
1112
onOpenChange: (open: boolean) => void;
12-
trigger?: 'habit_limit' | 'insights' | 'upgrade_card';
13+
trigger?: 'habit_limit' | 'insights' | 'upgrade_card' | string;
1314
}
1415

1516
export function PaywallDialog({ open, onOpenChange, trigger = 'upgrade_card' }: PaywallDialogProps) {
1617
const { upgradeToPro } = useApp();
1718

19+
// Track paywall viewed when opened
20+
useEffect(() => {
21+
if (open) {
22+
track(EVENTS.PAYWALL_VIEWED, { trigger });
23+
}
24+
}, [open, trigger]);
25+
1826
const handleUpgrade = () => {
19-
// TODO: Analytics - paywall upgrade clicked
20-
track(Events.PAYWALL_UPGRADE_CLICKED, { trigger });
27+
track(EVENTS.UPGRADE_CLICKED, { trigger });
2128

2229
upgradeToPro();
2330
toast.success("Welcome to Pro! 🎉", {
2431
description: "You now have access to all premium features.",
2532
});
2633

27-
// TODO: Analytics - upgrade success
28-
track(Events.UPGRADE_SUCCESS, { trigger });
34+
track(EVENTS.UPGRADE_SUCCEEDED, { trigger });
2935

3036
onOpenChange(false);
3137
};
3238

33-
// TODO: Analytics - paywall shown
34-
if (open) {
35-
track(Events.PAYWALL_SHOWN, { trigger });
36-
}
39+
const handleDismiss = (open: boolean) => {
40+
if (!open) {
41+
track(EVENTS.PAYWALL_DISMISSED, { trigger });
42+
}
43+
onOpenChange(open);
44+
};
3745

3846
const features = [
3947
{ name: "Active habits", free: "3", pro: "Unlimited" },
@@ -47,7 +55,7 @@ export function PaywallDialog({ open, onOpenChange, trigger = 'upgrade_card' }:
4755
];
4856

4957
return (
50-
<Dialog open={open} onOpenChange={onOpenChange}>
58+
<Dialog open={open} onOpenChange={handleDismiss}>
5159
<DialogContent className="sm:max-w-lg">
5260
<DialogHeader className="text-center pb-2">
5361
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-br from-primary to-orange-400">

src/components/layout/AppHeader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
1313
import { useApp } from "@/lib/store";
1414
import { useNavigate } from "react-router-dom";
1515
import { Menu, Settings, LogOut, User, Sparkles } from "lucide-react";
16-
import { track, Events } from "@/lib/events";
16+
import { track, EVENTS } from "@/lib/events";
1717

1818
interface AppHeaderProps {
1919
onMenuClick?: () => void;
@@ -26,8 +26,7 @@ export function AppHeader({ onMenuClick, showMenuButton = false }: AppHeaderProp
2626
const today = format(new Date(), "EEEE, MMMM d");
2727

2828
const handleSignOut = () => {
29-
// TODO: Analytics - sign out
30-
track(Events.SIGN_OUT);
29+
track(EVENTS.AUTH_SIGNOUT);
3130
signOut();
3231
navigate("/auth/sign-in");
3332
};

src/lib/analytics/events.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Analytics event names
3+
*
4+
* This file contains all event names as typed string literals.
5+
* Use these constants when calling track() to ensure consistency.
6+
*/
7+
8+
export const EVENTS = {
9+
// Auth events
10+
AUTH_SIGNIN_VIEWED: 'auth_signin_viewed',
11+
AUTH_SIGNIN_SUBMITTED: 'auth_signin_submitted',
12+
AUTH_SIGNIN_SUCCEEDED: 'auth_signin_succeeded',
13+
AUTH_SIGNIN_FAILED: 'auth_signin_failed',
14+
AUTH_SIGNUP_VIEWED: 'auth_signup_viewed',
15+
AUTH_SIGNUP_SUBMITTED: 'auth_signup_submitted',
16+
AUTH_SIGNUP_SUCCEEDED: 'auth_signup_succeeded',
17+
AUTH_SIGNOUT: 'auth_signout',
18+
19+
// Post-auth routing
20+
ROUTED_TO_ONBOARDING: 'routed_to_onboarding',
21+
ROUTED_TO_HOME: 'routed_to_home',
22+
23+
// Onboarding events
24+
ONBOARDING_STEP_VIEWED: 'onboarding_step_viewed',
25+
ONBOARDING_STEP_COMPLETED: 'onboarding_step_completed',
26+
ONBOARDING_GOAL_SELECTED: 'onboarding_goal_selected',
27+
ONBOARDING_HABIT_TOGGLED: 'onboarding_habit_toggled',
28+
ONBOARDING_REMINDER_SET: 'onboarding_reminder_set',
29+
ONBOARDING_REMINDER_SKIPPED: 'onboarding_reminder_skipped',
30+
NOTIFICATION_PERMISSION_PROMPT_ACCEPTED: 'notification_permission_prompt_accepted',
31+
NOTIFICATION_PERMISSION_PROMPT_DISMISSED: 'notification_permission_prompt_dismissed',
32+
ONBOARDING_COMPLETED: 'onboarding_completed',
33+
34+
// Home events
35+
HOME_VIEWED: 'home_viewed',
36+
HOME_CTA_LOG_CLICKED: 'home_cta_log_clicked',
37+
38+
// Log events
39+
LOG_TODAY_VIEWED: 'log_today_viewed',
40+
HABIT_LOG_TOGGLED: 'habit_log_toggled',
41+
HABIT_LOG_VALUE_CHANGED: 'habit_log_value_changed',
42+
DAY_COMPLETED_CLICKED: 'day_completed_clicked',
43+
DAY_COMPLETED_SUCCEEDED: 'day_completed_succeeded',
44+
DAY_COMPLETION_DIALOG_SHOWN: 'day_completion_dialog_shown',
45+
DAY_COMPLETION_VIEW_INSIGHTS_CLICKED: 'day_completion_view_insights_clicked',
46+
47+
// Habits events
48+
HABITS_VIEWED: 'habits_viewed',
49+
HABIT_ADD_CLICKED: 'habit_add_clicked',
50+
HABIT_ADD_DIALOG_OPENED: 'habit_add_dialog_opened',
51+
HABIT_CREATED: 'habit_created',
52+
HABIT_UPDATED: 'habit_updated',
53+
HABIT_ARCHIVED: 'habit_archived',
54+
HABIT_REACTIVATED: 'habit_reactivated',
55+
56+
// Insights events
57+
INSIGHTS_VIEWED: 'insights_viewed',
58+
INSIGHTS_WEEK_CHANGED: 'insights_week_changed',
59+
PRO_INSIGHTS_LOCKED_VIEWED: 'pro_insights_locked_viewed',
60+
61+
// Settings events
62+
SETTINGS_VIEWED: 'settings_viewed',
63+
PLAN_SECTION_VIEWED: 'plan_section_viewed',
64+
DEMO_RESET_CLICKED: 'demo_reset_clicked',
65+
DEMO_RESET_CONFIRMED: 'demo_reset_confirmed',
66+
67+
// Paywall events
68+
PAYWALL_TRIGGERED: 'paywall_triggered',
69+
PAYWALL_VIEWED: 'paywall_viewed',
70+
UPGRADE_CLICKED: 'upgrade_clicked',
71+
UPGRADE_SUCCEEDED: 'upgrade_succeeded',
72+
PAYWALL_DISMISSED: 'paywall_dismissed',
73+
} as const;
74+
75+
// Export type for TypeScript users
76+
export type EventName = typeof EVENTS[keyof typeof EVENTS];

src/lib/analytics/track.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Analytics tracking module
3+
*
4+
* TODO: Journium SDK integration
5+
* When ready to integrate Journium SDK:
6+
* 1. Replace console.log with journium.track(eventName, properties)
7+
* 2. Initialize Journium SDK in main.tsx or App.tsx
8+
* 3. Remove the feature flag check if Journium handles it
9+
*/
10+
11+
// Feature flag key in localStorage
12+
const ANALYTICS_ENABLED_KEY = 'analyticsEnabled';
13+
const SESSION_ID_KEY = 'sessionId';
14+
15+
// Generate a session ID for this tab session
16+
function getSessionId(): string {
17+
let sessionId = sessionStorage.getItem(SESSION_ID_KEY);
18+
if (!sessionId) {
19+
sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
20+
sessionStorage.setItem(SESSION_ID_KEY, sessionId);
21+
}
22+
return sessionId;
23+
}
24+
25+
// Check if analytics is enabled
26+
function isAnalyticsEnabled(): boolean {
27+
const enabled = localStorage.getItem(ANALYTICS_ENABLED_KEY);
28+
// Default to true if not set (first time)
29+
if (enabled === null) {
30+
localStorage.setItem(ANALYTICS_ENABLED_KEY, 'true');
31+
return true;
32+
}
33+
return enabled === 'true';
34+
}
35+
36+
// Get device type
37+
function getDevice(): 'mobile' | 'desktop' {
38+
return window.innerWidth < 768 ? 'mobile' : 'desktop';
39+
}
40+
41+
// Get user ID from localStorage (if logged in)
42+
function getUserId(): string | null {
43+
try {
44+
const userStr = localStorage.getItem('looply_user');
45+
if (userStr) {
46+
const user = JSON.parse(userStr);
47+
return user.id || null;
48+
}
49+
} catch (e) {
50+
// Ignore parse errors
51+
}
52+
return null;
53+
}
54+
55+
// Get user plan from localStorage
56+
function getUserPlan(): 'free' | 'pro' | null {
57+
try {
58+
const userStr = localStorage.getItem('looply_user');
59+
if (userStr) {
60+
const user = JSON.parse(userStr);
61+
return user.plan || null;
62+
}
63+
} catch (e) {
64+
// Ignore parse errors
65+
}
66+
return null;
67+
}
68+
69+
/**
70+
* Track an analytics event
71+
*
72+
* @param eventName - The name of the event to track
73+
* @param props - Optional properties to attach to the event
74+
*/
75+
export function track(eventName: string, props?: Record<string, any>): void {
76+
// Check feature flag
77+
if (!isAnalyticsEnabled()) {
78+
return;
79+
}
80+
81+
// Build common properties
82+
const commonProps = {
83+
ts: new Date().toISOString(),
84+
path: window.location.pathname,
85+
userId: getUserId(),
86+
plan: getUserPlan(),
87+
device: getDevice(),
88+
sessionId: getSessionId(),
89+
};
90+
91+
// Merge with provided properties
92+
const allProps = {
93+
...commonProps,
94+
...props,
95+
};
96+
97+
// TODO: replace console.log with journium.track(eventName, allProps)
98+
console.log('[track]', eventName, allProps);
99+
}
100+
101+
/**
102+
* Enable or disable analytics tracking
103+
*/
104+
export function setAnalyticsEnabled(enabled: boolean): void {
105+
localStorage.setItem(ANALYTICS_ENABLED_KEY, enabled ? 'true' : 'false');
106+
}
107+
108+
/**
109+
* Check if analytics is currently enabled
110+
*/
111+
export function getAnalyticsEnabled(): boolean {
112+
return isAnalyticsEnabled();
113+
}
114+
115+
/**
116+
* Get the current session ID
117+
*/
118+
export function getCurrentSessionId(): string {
119+
return getSessionId();
120+
}

src/lib/events.ts

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,8 @@
11
/**
2-
* Analytics events placeholder
3-
* Replace with your actual analytics implementation (e.g., Mixpanel, Amplitude, Segment)
2+
* Analytics events - Re-exports from analytics module
3+
* This file provides backward compatibility with existing imports
44
*/
55

6-
type EventProperties = Record<string, unknown>;
7-
8-
export function track(eventName: string, properties?: EventProperties): void {
9-
// TODO: Implement actual analytics tracking
10-
console.log(`[Analytics] ${eventName}`, properties);
11-
}
12-
13-
// Event name constants for consistency
14-
export const Events = {
15-
// Auth events
16-
SIGN_IN: 'sign_in',
17-
SIGN_UP: 'sign_up',
18-
SIGN_OUT: 'sign_out',
19-
20-
// Onboarding events
21-
ONBOARDING_STARTED: 'onboarding_started',
22-
ONBOARDING_STEP_VIEWED: 'onboarding_step_viewed',
23-
ONBOARDING_GOAL_SELECTED: 'onboarding_goal_selected',
24-
ONBOARDING_HABITS_SELECTED: 'onboarding_habits_selected',
25-
ONBOARDING_COMPLETED: 'onboarding_completed',
26-
27-
// Notification events
28-
NOTIFICATION_PERMISSION_ALLOWED: 'notification_permission_allowed',
29-
NOTIFICATION_PERMISSION_DENIED: 'notification_permission_denied',
30-
31-
// Habit events
32-
HABIT_CREATED: 'habit_created',
33-
HABIT_UPDATED: 'habit_updated',
34-
HABIT_ARCHIVED: 'habit_archived',
35-
HABIT_LOGGED: 'habit_logged',
36-
37-
// Paywall events
38-
PAYWALL_SHOWN: 'paywall_shown',
39-
PAYWALL_UPGRADE_CLICKED: 'paywall_upgrade_clicked',
40-
UPGRADE_SUCCESS: 'upgrade_success',
41-
42-
// General events
43-
PAGE_VIEW: 'page_view',
44-
DEMO_DATA_RESET: 'demo_data_reset',
45-
} as const;
6+
// Re-export everything from the new analytics module
7+
export { track, setAnalyticsEnabled, getAnalyticsEnabled, getCurrentSessionId } from './analytics/track';
8+
export { EVENTS, EVENTS as Events } from './analytics/events';

0 commit comments

Comments
 (0)