Skip to content

Commit 64eb664

Browse files
authored
Merge pull request #216 from Chigybillionz/analytics-and-user-behaviour
[Frontend] Add analytics and user behavior tracking
2 parents 13f14ad + 08abd1e commit 64eb664

5 files changed

Lines changed: 135 additions & 11 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { useEffect, useState } from 'react';
2+
import Cookies from 'js-cookie';
3+
4+
export function CookieConsentBanner() {
5+
const [showBanner, setShowBanner] = useState(false);
6+
7+
useEffect(() => {
8+
const consent = Cookies.get('analytics-consent');
9+
if (!consent) {
10+
setShowBanner(true);
11+
}
12+
}, []);
13+
14+
const acceptCookies = () => {
15+
Cookies.set('analytics-consent', 'accepted', { expires: 365 });
16+
setShowBanner(false);
17+
window.location.reload();
18+
};
19+
20+
const declineCookies = () => {
21+
Cookies.set('analytics-consent', 'declined', { expires: 365 });
22+
setShowBanner(false);
23+
};
24+
25+
if (!showBanner) return null;
26+
27+
return (
28+
<div className="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4 z-50 shadow-lg border-t border-gray-800">
29+
<div className="container mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
30+
<div className="text-sm">
31+
<p className="font-semibold mb-1">We value your privacy</p>
32+
<p className="text-gray-300">We use privacy-friendly analytics to understand how you use StarkEd so we can improve the platform. Your data remains anonymous.</p>
33+
</div>
34+
<div className="flex gap-3 flex-shrink-0">
35+
<button
36+
onClick={declineCookies}
37+
className="px-4 py-2 text-sm font-medium text-gray-300 hover:text-white transition-colors"
38+
>
39+
Decline
40+
</button>
41+
<button
42+
onClick={acceptCookies}
43+
className="px-4 py-2 text-sm font-medium bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
44+
>
45+
Accept Analytics
46+
</button>
47+
</div>
48+
</div>
49+
</div>
50+
);
51+
}

frontend/src/components/CredentialList.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,19 @@ export function CredentialList({
138138
}, [credentials]);
139139

140140
const handleVerifyCredential = async (credentialId: string) => {
141-
await updateCredentialStatus(credentialId, 'pending');
141+
try {
142+
// Track credential verification
143+
if (typeof window !== 'undefined' && (window as any).plausible) {
144+
(window as any).plausible('Credential Verification', {
145+
props: { credentialId }
146+
});
147+
}
148+
149+
const credential = credentials.find(c => c.id === credentialId);
150+
await updateCredentialStatus(credentialId, 'pending');
151+
} catch (error) {
152+
console.error('Error verifying credential:', error);
153+
}
142154
};
143155

144156
const handleDownloadCredential = (credential: Credential) => {

frontend/src/pages/_app.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@ import { useRouter } from 'next/router';
44
import Head from 'next/head';
55
import { appWithTranslation } from 'next-i18next';
66
import { ThemeProvider } from 'next-themes';
7+
import PlausibleProvider from 'next-plausible';
78
import nextI18NextConfig from '../../next-i18next.config';
89
import { WalletProvider } from '../context/WalletContext';
910
import { ErrorBoundary } from '../components/ErrorBoundary';
1011
import { GlobalShell } from '../components/PWA/GlobalShell';
11-
import PWAInstallPrompt from '../components/PWAInstallPrompt';
12+
import { CookieConsentBanner } from '../components/CookieConsentBanner';
1213
import { Toaster } from 'react-hot-toast';
1314
import '../styles/globals.css';
1415

16+
export function reportWebVitals(metric: any) {
17+
if (typeof window !== 'undefined' && (window as any).plausible) {
18+
(window as any).plausible('Web Vitals', {
19+
props: {
20+
metric: metric.name,
21+
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
22+
},
23+
});
24+
}
25+
}
26+
1527
function MyApp({ Component, pageProps }: AppProps) {
1628
const router = useRouter();
1729
const hasMounted = useRef(false);
@@ -37,13 +49,7 @@ function MyApp({ Component, pageProps }: AppProps) {
3749
}, [router.asPath]);
3850

3951
return (
40-
<>
41-
<Head>
42-
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
43-
<link rel="manifest" href="/manifest.json" />
44-
<meta name="theme-color" content="#3b82f6" />
45-
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
46-
</Head>
52+
<PlausibleProvider domain="starked-education.com" trackLocalhost={true}>
4753
<ThemeProvider attribute="class" defaultTheme="system" enableSystem storageKey="starked-theme">
4854
<ErrorBoundary key={router.asPath}>
4955
<WalletProvider>
@@ -60,7 +66,7 @@ function MyApp({ Component, pageProps }: AppProps) {
6066
</div>
6167
<GlobalShell />
6268
<Component {...pageProps} />
63-
<PWAInstallPrompt />
69+
<CookieConsentBanner />
6470
<Toaster
6571
position="bottom-right"
6672
toastOptions={{
@@ -70,7 +76,7 @@ function MyApp({ Component, pageProps }: AppProps) {
7076
</WalletProvider>
7177
</ErrorBoundary>
7278
</ThemeProvider>
73-
</>
79+
</PlausibleProvider>
7480
);
7581
}
7682

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import Head from 'next/head';
3+
4+
export default function AnalyticsDashboard() {
5+
return (
6+
<>
7+
<Head>
8+
<title>Analytics Dashboard | StarkEd Admin</title>
9+
</Head>
10+
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-8">
11+
<div className="max-w-7xl mx-auto">
12+
<div className="mb-8">
13+
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Analytics Dashboard</h1>
14+
<p className="text-gray-600 dark:text-gray-400 mt-2">
15+
View platform usage, user engagement, and performance metrics.
16+
</p>
17+
</div>
18+
19+
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden min-h-[800px] flex flex-col">
20+
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex justify-between items-center">
21+
<h2 className="font-semibold text-gray-800 dark:text-gray-200">Plausible Analytics</h2>
22+
<a
23+
href="https://plausible.io/starked-education.com"
24+
target="_blank"
25+
rel="noopener noreferrer"
26+
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium"
27+
>
28+
Open in Plausible
29+
</a>
30+
</div>
31+
<div className="flex-grow w-full h-full p-0">
32+
<iframe
33+
plausible-embed="true"
34+
src="https://plausible.io/share/starked-education.com?auth=YOUR_AUTH_TOKEN&theme=system"
35+
scrolling="no"
36+
frameBorder="0"
37+
loading="lazy"
38+
style={{ width: '100%', height: '100%', minHeight: '800px' }}
39+
title="Plausible Analytics"
40+
></iframe>
41+
<script async src="https://plausible.io/js/embed.host.js"></script>
42+
</div>
43+
</div>
44+
</div>
45+
</div>
46+
</>
47+
);
48+
}

frontend/src/store/courseStore.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ export const useCourseStore = create<CourseStore>()(
8989
set((state) => ({
9090
enrolledCourses: [...state.enrolledCourses, enrolledCourse]
9191
}));
92+
93+
// Track course enrollment
94+
if (typeof window !== 'undefined' && (window as any).plausible) {
95+
(window as any).plausible('Course Enrollment', {
96+
props: { courseId: course.id, courseTitle: course.title }
97+
});
98+
}
9299
}
93100
},
94101

0 commit comments

Comments
 (0)