Add events handling and enhance analytics tracking (#47)
* Add events handling and enhance analytics tracking Added a new events system to track user actions throughout the application. Specific significant events such as user signup, sign-in, and checkout have dedicated handlers. Updated the analytics system to handle these event triggers and improved analytics reporting. An analytics provider has been implemented to manage event subscriptions and analytics event mappings. * Remove unused dependencies from package.json files Unused packages "@tanstack/react-table" and "next" have been removed from the packages/shared and tooling directories respectively. These changes help ensure that only needed packages are included in the project, reducing potential security risks and unnecessary processing overhead. * Update dependencies Multiple package versions were updated including "@tanstack/react-query" and "lucide-react"
This commit is contained in:
committed by
GitHub
parent
868f907c81
commit
5eefa7ff16
95
apps/web/components/analytics-provider.tsx
Normal file
95
apps/web/components/analytics-provider.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { analytics } from '@kit/analytics';
|
||||
import {
|
||||
AppEvent,
|
||||
AppEventType,
|
||||
ConsumerProvidedEventTypes,
|
||||
useAppEvents,
|
||||
} from '@kit/shared/events';
|
||||
|
||||
type AnalyticsMapping<
|
||||
T extends ConsumerProvidedEventTypes = NonNullable<unknown>,
|
||||
> = {
|
||||
[K in AppEventType<T>]?: (event: AppEvent<T, K>) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to subscribe to app events and map them to analytics actions
|
||||
* @param mapping
|
||||
*/
|
||||
function useAnalyticsMapping<T extends ConsumerProvidedEventTypes>(
|
||||
mapping: AnalyticsMapping<T>,
|
||||
) {
|
||||
const appEvents = useAppEvents<T>();
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = Object.entries(mapping).map(
|
||||
([eventType, handler]) => {
|
||||
appEvents.on(eventType as AppEventType<T>, handler);
|
||||
|
||||
return () => appEvents.off(eventType as AppEventType<T>, handler);
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, [appEvents, mapping]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a mapping of app events to analytics actions
|
||||
* Add new mappings here to track new events in the analytics service from app events
|
||||
*/
|
||||
const analyticsMapping: AnalyticsMapping = {
|
||||
'user.signedIn': (event) => {
|
||||
const userId = event.payload.userId;
|
||||
|
||||
if (userId) {
|
||||
analytics.identify(userId);
|
||||
}
|
||||
},
|
||||
'user.signedUp': (event) => {
|
||||
analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
'checkout.started': (event) => {
|
||||
analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
'user.updated': (event) => {
|
||||
analytics.trackEvent(event.type, event.payload);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider for the analytics service
|
||||
*/
|
||||
export function AnalyticsProvider(props: React.PropsWithChildren) {
|
||||
// Subscribe to app events and map them to analytics actions
|
||||
useAnalyticsMapping(analyticsMapping);
|
||||
|
||||
// Report page views to the analytics service
|
||||
useReportPageView((url) => analytics.trackPageView(url));
|
||||
|
||||
// Render children
|
||||
return props.children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to report page views to the analytics service
|
||||
* @param reportAnalyticsFn
|
||||
*/
|
||||
function useReportPageView(reportAnalyticsFn: (url: string) => void) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
const url = [pathname, searchParams.toString()].filter(Boolean).join('?');
|
||||
|
||||
reportAnalyticsFn(url);
|
||||
}, [pathname, reportAnalyticsFn, searchParams]);
|
||||
}
|
||||
48
apps/web/components/auth-provider.tsx
Normal file
48
apps/web/components/auth-provider.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useAppEvents } from '@kit/shared/events';
|
||||
import { useAuthChangeListener } from '@kit/supabase/hooks/use-auth-change-listener';
|
||||
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
|
||||
export function AuthProvider(props: React.PropsWithChildren) {
|
||||
const dispatchEvent = useDispatchAppEventFromAuthEvent();
|
||||
|
||||
useAuthChangeListener({
|
||||
appHomePath: pathsConfig.app.home,
|
||||
onEvent: (event, session) => {
|
||||
dispatchEvent(event, session?.user.id);
|
||||
},
|
||||
});
|
||||
|
||||
return props.children;
|
||||
}
|
||||
|
||||
function useDispatchAppEventFromAuthEvent() {
|
||||
const { emit } = useAppEvents();
|
||||
|
||||
return useCallback(
|
||||
(type: string, userId: string | undefined) => {
|
||||
switch (type) {
|
||||
case 'SIGNED_IN':
|
||||
emit({
|
||||
type: 'user.signedIn',
|
||||
payload: { userId: userId! },
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 'USER_UPDATED':
|
||||
emit({
|
||||
type: 'user.updated',
|
||||
payload: { userId: userId! },
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
[emit],
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
|
||||
@@ -8,14 +10,15 @@ import { ThemeProvider } from 'next-themes';
|
||||
import { CaptchaProvider } from '@kit/auth/captcha/client';
|
||||
import { I18nProvider } from '@kit/i18n/provider';
|
||||
import { MonitoringProvider } from '@kit/monitoring/components';
|
||||
import { useAuthChangeListener } from '@kit/supabase/hooks/use-auth-change-listener';
|
||||
import { AppEventsProvider } from '@kit/shared/events';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { VersionUpdater } from '@kit/ui/version-updater';
|
||||
|
||||
import { AnalyticsProvider } from '~/components/analytics-provider';
|
||||
import { AuthProvider } from '~/components/auth-provider';
|
||||
import appConfig from '~/config/app.config';
|
||||
import authConfig from '~/config/auth.config';
|
||||
import featuresFlagConfig from '~/config/feature-flags.config';
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
import { i18nResolver } from '~/lib/i18n/i18n.resolver';
|
||||
import { getI18nSettings } from '~/lib/i18n/i18n.settings';
|
||||
|
||||
@@ -43,44 +46,39 @@ export function RootProviders({
|
||||
lang: string;
|
||||
theme?: string;
|
||||
}>) {
|
||||
const i18nSettings = getI18nSettings(lang);
|
||||
const i18nSettings = useMemo(() => getI18nSettings(lang), [lang]);
|
||||
|
||||
return (
|
||||
<MonitoringProvider>
|
||||
<ReactQueryProvider>
|
||||
<ReactQueryStreamedHydration>
|
||||
<I18nProvider settings={i18nSettings} resolver={i18nResolver}>
|
||||
<CaptchaProvider>
|
||||
<CaptchaTokenSetter siteKey={captchaSiteKey} />
|
||||
<AppEventsProvider>
|
||||
<AnalyticsProvider>
|
||||
<ReactQueryProvider>
|
||||
<ReactQueryStreamedHydration>
|
||||
<I18nProvider settings={i18nSettings} resolver={i18nResolver}>
|
||||
<CaptchaProvider>
|
||||
<CaptchaTokenSetter siteKey={captchaSiteKey} />
|
||||
|
||||
<AuthProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
defaultTheme={theme}
|
||||
enableColorScheme={false}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</CaptchaProvider>
|
||||
<AuthProvider>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
defaultTheme={theme}
|
||||
enableColorScheme={false}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</CaptchaProvider>
|
||||
|
||||
<If condition={featuresFlagConfig.enableVersionUpdater}>
|
||||
<VersionUpdater />
|
||||
</If>
|
||||
</I18nProvider>
|
||||
</ReactQueryStreamedHydration>
|
||||
</ReactQueryProvider>
|
||||
<If condition={featuresFlagConfig.enableVersionUpdater}>
|
||||
<VersionUpdater />
|
||||
</If>
|
||||
</I18nProvider>
|
||||
</ReactQueryStreamedHydration>
|
||||
</ReactQueryProvider>
|
||||
</AnalyticsProvider>
|
||||
</AppEventsProvider>
|
||||
</MonitoringProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// we place this below React Query since it uses the QueryClient
|
||||
function AuthProvider(props: React.PropsWithChildren) {
|
||||
useAuthChangeListener({
|
||||
appHomePath: pathsConfig.app.home,
|
||||
});
|
||||
|
||||
return props.children;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user