Add PostHog analytics events matching iOS/KMM event names
- Define AnalyticsEvents constants matching iOS AnalyticsEvent enum - Track user_signed_in, user_registered with identify/reset - Track residence_created, task_created, contractor_created, document_created - Track residence_shared, theme_changed - All events include platform:'web' for cross-platform filtering - Reset PostHog on logout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,3 +45,38 @@ export function resetAnalytics() {
|
|||||||
posthog.reset();
|
posthog.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Event names — match iOS/KMM AnalyticsEvents for cross-platform consistency
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const AnalyticsEvents = {
|
||||||
|
// Auth
|
||||||
|
USER_REGISTERED: "user_registered",
|
||||||
|
USER_SIGNED_IN: "user_signed_in",
|
||||||
|
|
||||||
|
// Residences
|
||||||
|
RESIDENCE_CREATED: "residence_created",
|
||||||
|
RESIDENCE_LIMIT_REACHED: "residence_limit_reached",
|
||||||
|
RESIDENCE_SHARED: "residence_shared",
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
TASK_CREATED: "task_created",
|
||||||
|
|
||||||
|
// Contractors
|
||||||
|
CONTRACTOR_CREATED: "contractor_created",
|
||||||
|
CONTRACTOR_PAYWALL_SHOWN: "contractor_paywall_shown",
|
||||||
|
|
||||||
|
// Documents
|
||||||
|
DOCUMENT_CREATED: "document_created",
|
||||||
|
DOCUMENTS_PAYWALL_SHOWN: "documents_paywall_shown",
|
||||||
|
|
||||||
|
// Sharing
|
||||||
|
SHARE_RESIDENCE_PAYWALL_SHOWN: "share_residence_paywall_shown",
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
THEME_CHANGED: "theme_changed",
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
ERROR_OCCURRED: "error_occurred",
|
||||||
|
} as const;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
||||||
|
import { trackEvent, AnalyticsEvents } from '@/lib/analytics';
|
||||||
import type { CreateContractorRequest, UpdateContractorRequest } from '@/lib/api/contractors';
|
import type { CreateContractorRequest, UpdateContractorRequest } from '@/lib/api/contractors';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -51,6 +52,7 @@ export function useCreateContractor() {
|
|||||||
mutationFn: (data: CreateContractorRequest) =>
|
mutationFn: (data: CreateContractorRequest) =>
|
||||||
contractors.create(data),
|
contractors.create(data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
trackEvent(AnalyticsEvents.CONTRACTOR_CREATED, { platform: 'web' });
|
||||||
queryClient.invalidateQueries({ queryKey: qk('contractors') });
|
queryClient.invalidateQueries({ queryKey: qk('contractors') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
||||||
import type { DocumentListParams, CreateDocumentRequest, UpdateDocumentRequest } from '@/lib/api/documents';
|
import { trackEvent, AnalyticsEvents } from '@/lib/analytics';
|
||||||
|
import type { DocumentListParams, CreateDocumentRequest, UpdateDocumentRequest, DocumentResponse } from '@/lib/api/documents';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Query hooks
|
// Query hooks
|
||||||
@@ -49,7 +50,11 @@ export function useCreateDocument() {
|
|||||||
}
|
}
|
||||||
return documents.create(data);
|
return documents.create(data);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (result: DocumentResponse) => {
|
||||||
|
trackEvent(AnalyticsEvents.DOCUMENT_CREATED, {
|
||||||
|
type: result.document_type,
|
||||||
|
platform: 'web',
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: qk('documents') });
|
queryClient.invalidateQueries({ queryKey: qk('documents') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
||||||
import type { CreateResidenceRequest, UpdateResidenceRequest } from '@/lib/api/residences';
|
import { trackEvent, AnalyticsEvents } from '@/lib/analytics';
|
||||||
|
import type { CreateResidenceRequest, UpdateResidenceRequest, ResidenceResponse } from '@/lib/api/residences';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Query hooks
|
// Query hooks
|
||||||
@@ -39,7 +40,11 @@ export function useCreateResidence() {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (data: CreateResidenceRequest) =>
|
mutationFn: (data: CreateResidenceRequest) =>
|
||||||
residences.create(data),
|
residences.create(data),
|
||||||
onSuccess: () => {
|
onSuccess: (result: ResidenceResponse) => {
|
||||||
|
trackEvent(AnalyticsEvents.RESIDENCE_CREATED, {
|
||||||
|
residence_type: result.property_type?.name,
|
||||||
|
platform: 'web',
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: qk('residences') });
|
queryClient.invalidateQueries({ queryKey: qk('residences') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
||||||
|
import { trackEvent, AnalyticsEvents } from '@/lib/analytics';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Query hooks
|
// Query hooks
|
||||||
@@ -38,6 +39,7 @@ export function useGenerateShareCode(residenceId: number) {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: () => sharing.generateShareCode(residenceId),
|
mutationFn: () => sharing.generateShareCode(residenceId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
trackEvent(AnalyticsEvents.RESIDENCE_SHARED, { method: 'code', platform: 'web' });
|
||||||
queryClient.invalidateQueries({ queryKey: qk('residences', residenceId, 'share-code') });
|
queryClient.invalidateQueries({ queryKey: qk('residences', residenceId, 'share-code') });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
import { useDataProvider, useQueryKeyPrefix } from '@/lib/demo/data-provider-context';
|
||||||
import type { CreateTaskRequest, UpdateTaskRequest, CreateCompletionRequest } from '@/lib/api/tasks';
|
import { trackEvent, AnalyticsEvents } from '@/lib/analytics';
|
||||||
|
import type { CreateTaskRequest, UpdateTaskRequest, CreateCompletionRequest, TaskResponse } from '@/lib/api/tasks';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Query hooks
|
// Query hooks
|
||||||
@@ -67,7 +68,11 @@ export function useCreateTask() {
|
|||||||
const qk = useQueryKeyPrefix();
|
const qk = useQueryKeyPrefix();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (data: CreateTaskRequest) => tasks.create(data),
|
mutationFn: (data: CreateTaskRequest) => tasks.create(data),
|
||||||
onSuccess: () => {
|
onSuccess: (result: TaskResponse) => {
|
||||||
|
trackEvent(AnalyticsEvents.TASK_CREATED, {
|
||||||
|
residence_id: result.residence_id,
|
||||||
|
platform: 'web',
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: qk('tasks') });
|
queryClient.invalidateQueries({ queryKey: qk('tasks') });
|
||||||
queryClient.invalidateQueries({ queryKey: qk('residences') });
|
queryClient.invalidateQueries({ queryKey: qk('residences') });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import * as authApi from '@/lib/api/auth';
|
import * as authApi from '@/lib/api/auth';
|
||||||
import { getQueryClient } from '@/lib/query/query-client';
|
import { getQueryClient } from '@/lib/query/query-client';
|
||||||
|
import { trackEvent, identifyUser, resetAnalytics, AnalyticsEvents } from '@/lib/analytics';
|
||||||
import type { UserResponse } from '@/lib/api/auth';
|
import type { UserResponse } from '@/lib/api/auth';
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
@@ -32,6 +33,11 @@ export const useAuthStore = create<AuthState>()((set) => ({
|
|||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
try {
|
try {
|
||||||
const response = await authApi.login(credentials);
|
const response = await authApi.login(credentials);
|
||||||
|
identifyUser(String(response.user.id), {
|
||||||
|
email: response.user.email,
|
||||||
|
name: `${response.user.first_name} ${response.user.last_name}`.trim(),
|
||||||
|
});
|
||||||
|
trackEvent(AnalyticsEvents.USER_SIGNED_IN, { method: 'email', platform: 'web' });
|
||||||
set({ user: response.user, isAuthenticated: true, isLoading: false });
|
set({ user: response.user, isAuthenticated: true, isLoading: false });
|
||||||
window.location.href = '/app';
|
window.location.href = '/app';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -45,6 +51,7 @@ export const useAuthStore = create<AuthState>()((set) => ({
|
|||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
try {
|
try {
|
||||||
await authApi.register(data);
|
await authApi.register(data);
|
||||||
|
trackEvent(AnalyticsEvents.USER_REGISTERED, { method: 'email', platform: 'web' });
|
||||||
set({ isLoading: false });
|
set({ isLoading: false });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message =
|
const message =
|
||||||
@@ -65,6 +72,7 @@ export const useAuthStore = create<AuthState>()((set) => ({
|
|||||||
} finally {
|
} finally {
|
||||||
// Clear React Query cache to prevent stale data leaking into demo mode
|
// Clear React Query cache to prevent stale data leaking into demo mode
|
||||||
getQueryClient().clear();
|
getQueryClient().clear();
|
||||||
|
resetAnalytics();
|
||||||
set({
|
set({
|
||||||
user: null,
|
user: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
@@ -79,6 +87,10 @@ export const useAuthStore = create<AuthState>()((set) => ({
|
|||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
try {
|
try {
|
||||||
const user = await authApi.getCurrentUser();
|
const user = await authApi.getCurrentUser();
|
||||||
|
identifyUser(String(user.id), {
|
||||||
|
email: user.email,
|
||||||
|
name: `${user.first_name} ${user.last_name}`.trim(),
|
||||||
|
});
|
||||||
set({ user, isAuthenticated: true, isLoading: false });
|
set({ user, isAuthenticated: true, isLoading: false });
|
||||||
} catch {
|
} catch {
|
||||||
set({ user: null, isAuthenticated: false, isLoading: false });
|
set({ user: null, isAuthenticated: false, isLoading: false });
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,6 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
import { trackEvent, AnalyticsEvents } from "@/lib/analytics";
|
||||||
|
|
||||||
export type ColorMode = "light" | "dark" | "system";
|
export type ColorMode = "light" | "dark" | "system";
|
||||||
|
|
||||||
@@ -12,7 +13,10 @@ export const useThemeStore = create<ThemeState>()(
|
|||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
mode: "light",
|
mode: "light",
|
||||||
setMode: (mode: ColorMode) => set({ mode }),
|
setMode: (mode: ColorMode) => {
|
||||||
|
trackEvent(AnalyticsEvents.THEME_CHANGED, { theme: mode, platform: 'web' });
|
||||||
|
set({ mode });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "casera-theme",
|
name: "casera-theme",
|
||||||
|
|||||||
Reference in New Issue
Block a user