Skip to content

Commit 6b243c1

Browse files
committed
feat(analytics): implement event system and add login tracking
1 parent bc33314 commit 6b243c1

25 files changed

Lines changed: 195 additions & 244 deletions

src/app/(public)/courses/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { notFound } from 'next/navigation'
33

44
import { getCourse, getCourseLessons, getCourses } from '@/src/api/requests'
55
import { CourseDetails } from '@/src/components/course/course-details'
6-
import { CourseProvider } from '@/src/components/providers/course-provider'
76
import { getMediaSource } from '@/src/lib/utils'
7+
import { CourseProvider } from '@/src/providers/course-provider'
88

99
export async function generateStaticParams() {
1010
const courses = await getCourses()

src/app/layout.tsx

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { GoogleAnalytics } from '@next/third-parties/google'
21
import { GeistSans } from 'geist/font/sans'
32
import type { Metadata } from 'next'
43
import type { ReactNode } from 'react'
54

6-
import { YandexMetrika } from '../components/analitycs/yandex-metrika'
7-
import { BanChecker } from '../components/providers/ban-checker'
8-
import { FingerprintProvider } from '../components/providers/fingerprint-provider'
9-
import { PosthogProvider } from '../components/providers/posthog-provider'
10-
import { TanstackQueryProvider } from '../components/providers/tanstack-query-provider'
11-
import { ThemeProvider } from '../components/providers/theme-provider'
125
import { Toaster } from '../components/shared/sonner'
136
import { APP_CONFIG, SEO } from '../constants'
7+
import { YandexMetrikaScript } from '../lib/analytics/script-providers'
8+
import {
9+
AnalyticsProvider,
10+
BanChecker,
11+
FingerprintProvider,
12+
TanstackQueryProvider,
13+
ThemeProvider
14+
} from '../providers'
1415

1516
import '@/src/styles/globals.css'
1617

@@ -73,7 +74,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
7374
<html className={GeistSans.variable} lang='ru' suppressHydrationWarning>
7475
<body className='flex h-full w-full flex-col font-sans'>
7576
<TanstackQueryProvider>
76-
<PosthogProvider>
77+
<AnalyticsProvider>
7778
<FingerprintProvider>
7879
<ThemeProvider
7980
attribute='class'
@@ -99,25 +100,12 @@ export default function RootLayout({ children }: { children: ReactNode }) {
99100
{process.env['NEXT_PUBLIC_NODE_ENV'] ===
100101
'production' && (
101102
<>
102-
<YandexMetrika
103-
id={
104-
process.env[
105-
'NEXT_PUBLIC_YANDEX_METRIKA_ID'
106-
] ?? ''
107-
}
108-
/>
109-
<GoogleAnalytics
110-
gaId={
111-
process.env[
112-
'NEXT_PUBLIC_GOOGLE_ANALYTICS_ID'
113-
] ?? ''
114-
}
115-
/>
103+
<YandexMetrikaScript />
116104
</>
117105
)}
118106
</ThemeProvider>
119107
</FingerprintProvider>
120-
</PosthogProvider>
108+
</AnalyticsProvider>
121109
</TanstackQueryProvider>
122110
</body>
123111
</html>

src/components/analitycs/yandex-metrika.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/components/auth/login-form.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { useLogin } from '@/src/api/hooks'
2828
import { instance } from '@/src/api/instance'
2929
import { ROUTES } from '@/src/constants'
3030
import { useFingerprint } from '@/src/hooks'
31+
import { analytics } from '@/src/lib/analytics/events'
3132
import { setSessionToken } from '@/src/lib/cookies/session'
3233

3334
const loginSchema = z.object({
@@ -55,6 +56,8 @@ export function LoginForm() {
5556

5657
const { mutateAsync, isPending } = useLogin({
5758
onSuccess(data) {
59+
analytics.auth.login.success()
60+
5861
if ('ticket' in data && typeof data.ticket === 'string') {
5962
setTicket(data.ticket)
6063
setMethods(data.allowedMethods)
@@ -73,7 +76,10 @@ export function LoginForm() {
7376
}
7477
},
7578
onError(error: any) {
76-
toast.error(error.response?.data?.message ?? 'Ошибка при входе')
79+
const message = error.response?.data?.message ?? 'Ошибка при входе'
80+
analytics.auth.login.fail(message)
81+
82+
toast.error(message)
7783
}
7884
})
7985

@@ -93,6 +99,8 @@ export function LoginForm() {
9399
}, [form, form.reset, form.formState.isSubmitSuccessful])
94100

95101
async function onSubmit(values: Login) {
102+
analytics.auth.login.submit()
103+
96104
if (!values.captcha) {
97105
toast.warning('Пройдите капчу!')
98106
return
@@ -197,6 +205,7 @@ export function LoginForm() {
197205
size='lg'
198206
isLoading={isPending}
199207
className='w-full'
208+
onClick={() => analytics.auth.login.click()}
200209
>
201210
Продолжить
202211
</Button>

src/lib/analytics/events.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { track } from './index'
2+
3+
export const analytics = {
4+
auth: {
5+
login: {
6+
click() {
7+
track('auth_login_click')
8+
},
9+
submit() {
10+
track('auth_login_submit')
11+
},
12+
success() {
13+
track('auth_login_success')
14+
},
15+
fail(message?: string) {
16+
track('auth_login_fail', { message })
17+
}
18+
}
19+
}
20+
}

src/lib/analytics/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { consoleProvider, metrikaProvider, posthogProvider } from './providers'
2+
3+
const providers = [
4+
posthogProvider,
5+
metrikaProvider,
6+
...(process.env.NODE_ENV === 'development' ? [consoleProvider] : [])
7+
]
8+
9+
providers.forEach(p => p.init?.())
10+
11+
export function track(event: string, data?: Record<string, any>) {
12+
providers.forEach(p => p.track(event, data))
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const consoleProvider = {
2+
init() {
3+
console.log('%c[Analytics] Dev mode enabled', 'color: #4ade80')
4+
},
5+
6+
track(event: string, data?: Record<string, any>) {
7+
console.log('%c[Track]', 'color: #60a5fa', event, data)
8+
}
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './console'
2+
export * from './metrika'
3+
export * from './posthog'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
declare global {
2+
interface Window {
3+
ym?: any
4+
}
5+
}
6+
7+
export const metrikaProvider = {
8+
init() {},
9+
10+
track(event: string, data?: Record<string, any>) {
11+
if (typeof window !== 'undefined' && typeof window.ym === 'function') {
12+
window.ym(
13+
Number(process.env.NEXT_PUBLIC_YANDEX_METRIKA_ID),
14+
'reachGoal',
15+
event,
16+
data
17+
)
18+
}
19+
}
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import posthog from 'posthog-js'
2+
3+
export const posthogProvider = {
4+
init() {
5+
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, {
6+
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
7+
person_profiles: 'always',
8+
defaults: '2025-05-24'
9+
})
10+
},
11+
12+
track(event: string, data?: Record<string, any>) {
13+
posthog.capture(event, data)
14+
}
15+
}

0 commit comments

Comments
 (0)