Add plausible

This commit is contained in:
Andras Schmelczer 2026-02-22 23:14:42 +00:00
parent 48f2c97487
commit 4857800fca
14 changed files with 118 additions and 6 deletions

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import pb from '../lib/pocketbase';
import { trackEvent } from '../lib/analytics';
export interface AuthUser {
id: string;
@ -52,6 +53,7 @@ export function useAuth() {
try {
const result = await pb.collection('users').authWithPassword(email, password);
setUser(recordToUser(result.record));
trackEvent('Login', { method: 'email' });
} catch (err) {
const msg = err instanceof Error ? err.message : 'Login failed';
setError(msg);
@ -73,6 +75,7 @@ export function useAuth() {
// Auto-login after registration
const result = await pb.collection('users').authWithPassword(email, password);
setUser(recordToUser(result.record));
trackEvent('Register');
} catch (err) {
const msg = err instanceof Error ? err.message : 'Registration failed';
setError(msg);
@ -88,6 +91,7 @@ export function useAuth() {
try {
const result = await pb.collection('users').authWithOAuth2({ provider });
setUser(recordToUser(result.record));
trackEvent('Login', { method: provider });
} catch (err) {
const msg = err instanceof Error ? err.message : 'OAuth login failed';
setError(msg);
@ -98,6 +102,7 @@ export function useAuth() {
}, []);
const logout = useCallback(() => {
trackEvent('Logout');
pb.authStore.clear();
setUser(null);
}, []);

View file

@ -1,5 +1,6 @@
import { useState, useCallback, useMemo } from 'react';
import type { FeatureMeta, FeatureFilters } from '../types';
import { trackEvent } from '../lib/analytics';
interface UseFiltersOptions {
initialFilters: FeatureFilters;
@ -29,6 +30,7 @@ export function useFilters({ initialFilters, features }: UseFiltersOptions) {
(name: string) => {
const meta = features.find((f) => f.name === name);
if (!meta) return;
trackEvent('Filter Add', { feature: name });
if (meta.type === 'enum' && meta.values) {
setFilters((prev) => ({ ...prev, [name]: [...meta.values!] }));
} else if (meta.type === 'numeric' && meta.histogram) {
@ -45,6 +47,7 @@ export function useFilters({ initialFilters, features }: UseFiltersOptions) {
}, []);
const handleRemoveFilter = useCallback((name: string) => {
trackEvent('Filter Remove', { feature: name });
setFilters((prev) => {
const next = { ...prev };
delete next[name];
@ -84,6 +87,7 @@ export function useFilters({ initialFilters, features }: UseFiltersOptions) {
}, []);
const handleTogglePin = useCallback((name: string) => {
trackEvent('Filter Pin', { feature: name });
setPinnedFeature((prev) => (prev === name ? null : name));
}, []);

View file

@ -1,4 +1,5 @@
import { useState, useCallback } from 'react';
import { trackEvent } from '../lib/analytics';
import type {
FeatureMeta,
FeatureFilters,
@ -107,6 +108,7 @@ export function useHexagonSelection({ filters, features, resolution }: UseHexago
setSelectedPostcodeGeometry(null);
} else {
const type = isPostcode ? 'postcode' : 'hexagon';
trackEvent('Hexagon Click', { type });
setSelectedHexagon({ id, type, resolution });
setSelectedPostcodeGeometry(isPostcode && geometry ? geometry : null);
setProperties([]);
@ -138,6 +140,7 @@ export function useHexagonSelection({ filters, features, resolution }: UseHexago
const handleViewPropertiesFromArea = useCallback(() => {
if (selectedHexagon && selectedHexagon.type === 'hexagon') {
trackEvent('View Properties');
setRightPaneTab('properties');
setPropertiesOffset(0);
fetchHexagonProperties(selectedHexagon.id, selectedHexagon.resolution, 0);
@ -167,6 +170,7 @@ export function useHexagonSelection({ filters, features, resolution }: UseHexago
const handleLocationSearch = useCallback(
(postcode: string, geometry: PostcodeGeometry) => {
trackEvent('Postcode Search');
setSelectedHexagon({ id: postcode, type: 'postcode', resolution });
setSelectedPostcodeGeometry(geometry);
setProperties([]);

View file

@ -1,11 +1,13 @@
import { useState, useCallback } from 'react';
import { apiUrl, authHeaders, assertOk } from '../lib/api';
import { trackEvent } from '../lib/analytics';
export function useLicense() {
const [checkingOut, setCheckingOut] = useState(false);
const [error, setError] = useState<string | null>(null);
const startCheckout = useCallback(async (referralCode?: string) => {
trackEvent('Checkout Start', { has_referral: String(!!referralCode) });
setCheckingOut(true);
setError(null);
try {
@ -22,6 +24,7 @@ export function useLicense() {
assertOk(res, 'Checkout');
const data = await res.json();
if (data.url) {
trackEvent('Checkout Redirect');
window.location.href = data.url;
}
} catch (err) {

View file

@ -7,5 +7,6 @@ plausibleInit({
captureOnLocalhost: true,
logging: true,
fileDownloads: true,
outboundLinks: true,
hashBasedRouting: true,
});

View file

@ -1,6 +1,7 @@
import { useState, useCallback } from 'react';
import pb from '../lib/pocketbase';
import { apiUrl, authHeaders } from '../lib/api';
import { trackEvent } from '../lib/analytics';
export interface SavedSearch {
id: string;
@ -66,6 +67,7 @@ export function useSavedSearches(userId: string | null) {
formData.append('screenshot', screenshotBlob, 'screenshot.png');
await pb.collection('saved_searches').create(formData);
trackEvent('Search Save');
await fetchSearches();
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to save search';
@ -82,6 +84,7 @@ export function useSavedSearches(userId: string | null) {
setError(null);
try {
await pb.collection('saved_searches').delete(id);
trackEvent('Search Delete');
setSearches((prev) => prev.filter((s) => s.id !== id));
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete search');