Add Matomo analytics tracking with Suspense fix

- Created MatomoAnalytics component with page view and event tracking
- Fixed Next.js build issue by wrapping useSearchParams in Suspense
- Added tracking for user interactions:
  - Search functionality
  - Table sorting
  - Filter changes (material, finish, color)
  - Dark mode toggles
  - Admin login success/failure
  - Admin filament create/update actions
- Updated Amplify environment variables via AWS CLI
- Analytics URL: https://analytics.demirix.dev (Site ID: 7)
- Only loads when environment variables are set

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DaX
2025-06-30 23:01:01 +02:00
parent 966d253a7e
commit a2ec640ecc
8 changed files with 129 additions and 10 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import '@/src/styles/select.css';
import { trackEvent } from './MatomoAnalytics';
interface EnhancedFiltersProps {
filters: {
@@ -34,7 +35,10 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
</label>
<select
value={filters.material}
onChange={(e) => onFilterChange({ ...filters, material: e.target.value })}
onChange={(e) => {
onFilterChange({ ...filters, material: e.target.value });
trackEvent('Filter', 'Material', e.target.value || 'All');
}}
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -61,7 +65,10 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
</label>
<select
value={filters.finish}
onChange={(e) => onFilterChange({ ...filters, finish: e.target.value })}
onChange={(e) => {
onFilterChange({ ...filters, finish: e.target.value });
trackEvent('Filter', 'Finish', e.target.value || 'All');
}}
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -97,7 +104,10 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
</label>
<select
value={filters.color}
onChange={(e) => onFilterChange({ ...filters, color: e.target.value })}
onChange={(e) => {
onFilterChange({ ...filters, color: e.target.value });
trackEvent('Filter', 'Color', e.target.value || 'All');
}}
className="custom-select w-full px-3 py-2 border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100
rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -115,11 +125,14 @@ export const EnhancedFilters: React.FC<EnhancedFiltersProps> = ({
{hasActiveFilters && (
<div className="mt-4 text-center">
<button
onClick={() => onFilterChange({
material: '',
finish: '',
color: ''
})}
onClick={() => {
onFilterChange({
material: '',
finish: '',
color: ''
});
trackEvent('Filter', 'Reset', 'All Filters');
}}
className="px-4 py-2 text-sm font-medium text-white bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-700 rounded-md transition-colors"
>
Reset filtere

View File

@@ -2,6 +2,7 @@ import React, { useState, useMemo, useEffect } from 'react';
import { Filament } from '@/src/types/filament';
import { EnhancedFilters } from './EnhancedFilters';
import { colorService } from '@/src/services/api';
import { trackEvent } from './MatomoAnalytics';
interface FilamentTableV2Props {
filaments: Filament[];
@@ -114,6 +115,7 @@ const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments }) => {
setSortField(field);
setSortOrder('asc');
}
trackEvent('Table', 'Sort', field);
};
return (
@@ -125,7 +127,12 @@ const FilamentTableV2: React.FC<FilamentTableV2Props> = ({ filaments }) => {
type="text"
placeholder="Pretraži po materijalu, boji..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onChange={(e) => {
setSearchTerm(e.target.value);
if (e.target.value) {
trackEvent('Search', 'Filament Search', e.target.value);
}
}}
className="w-full px-4 py-2 pl-10 pr-4 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-blue-500"
/>
<svg className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@@ -0,0 +1,76 @@
'use client'
import { useEffect, Suspense } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
interface MatomoAnalyticsProps {
siteId: string;
matomoUrl: string;
}
function MatomoTracker({ siteId, matomoUrl }: MatomoAnalyticsProps) {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
// Initialize Matomo
const _paq = (window as any)._paq = (window as any)._paq || [];
// Track page view
_paq.push(['setCustomUrl', window.location.href]);
_paq.push(['setDocumentTitle', document.title]);
_paq.push(['trackPageView']);
// Enable link tracking
_paq.push(['enableLinkTracking']);
// Set up Matomo tracker
if (!(window as any).MatomoInitialized) {
_paq.push(['setTrackerUrl', `${matomoUrl}/matomo.php`]);
_paq.push(['setSiteId', siteId]);
// Create script element
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = `${matomoUrl}/matomo.js`;
const firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode?.insertBefore(script, firstScript);
(window as any).MatomoInitialized = true;
}
}, [pathname, searchParams, siteId, matomoUrl]);
return null;
}
export function MatomoAnalytics({ siteId, matomoUrl }: MatomoAnalyticsProps) {
return (
<Suspense fallback={null}>
<MatomoTracker siteId={siteId} matomoUrl={matomoUrl} />
</Suspense>
);
}
// Helper function to track events
export function trackEvent(category: string, action: string, name?: string, value?: number) {
const _paq = (window as any)._paq = (window as any)._paq || [];
_paq.push(['trackEvent', category, action, name, value]);
}
// Helper function to track e-commerce
export function trackEcommerce(productName: string, price: number, quantity: number = 1) {
const _paq = (window as any)._paq = (window as any)._paq || [];
_paq.push(['addEcommerceItem',
productName, // Product name
productName, // Product SKU
'Filament', // Product category
price,
quantity
]);
_paq.push(['trackEcommerceOrder',
Date.now().toString(), // Order ID
price * quantity // Total value
]);
}