Merge pull request #14 from damjan1996/auto-claude/018-add-jsdoc-documentation-to-utility-modules-and-cus

auto-claude: 018-add-jsdoc-documentation-to-utility-modules-and-cus
This commit is contained in:
Damjan Savic
2026-01-25 19:46:32 +01:00
committed by GitHub
21 changed files with 499 additions and 24 deletions
+32
View File
@@ -1,12 +1,44 @@
/**
* Custom Hook for Async Operations
* Provides a reusable pattern for handling asynchronous operations with loading, error, and data states
*/
import { useState, useCallback } from 'react';
import { handleError } from '../utils/errorHandling';
/**
* State interface for async operations
* @template T - The type of data returned by the async operation
*/
interface AsyncState<T> {
/** The data returned from the async operation, null if not yet loaded or error occurred */
data: T | null;
/** Error object if the operation failed, null otherwise */
error: Error | null;
/** Loading state indicator, true while operation is in progress */
loading: boolean;
}
/**
* Custom hook for managing asynchronous operations
* Handles loading states, error handling, and data management for async functions
*
* @template T - The type of data returned by the async operation
* @returns Object containing:
* - data: The result of the async operation or null
* - error: Any error that occurred or null
* - loading: Boolean indicating if operation is in progress
* - execute: Function to trigger the async operation
*
* @example
* ```tsx
* const { data, error, loading, execute } = useAsync<User>();
*
* useEffect(() => {
* execute(() => fetchUser(userId));
* }, [userId]);
* ```
*/
export function useAsync<T>() {
const [state, setState] = useState<AsyncState<T>>({
data: null,
+16
View File
@@ -1,5 +1,21 @@
/**
* Click Outside Detection Hook
* Detects clicks outside of a referenced element and triggers a handler
*/
import { RefObject, useEffect } from 'react';
/**
* Hook that triggers a handler when user clicks outside the referenced element
* Useful for closing dropdowns, modals, or menus when clicking outside
*
* @param ref - React ref object pointing to the element to monitor
* @param handler - Callback function to execute when click occurs outside the element
*
* @example
* const menuRef = useRef<HTMLDivElement>(null);
* useOnClickOutside(menuRef, () => setMenuOpen(false));
*/
export function useOnClickOutside(ref: RefObject<HTMLElement>, handler: (event: MouseEvent | TouchEvent) => void) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
+12 -1
View File
@@ -1,7 +1,18 @@
// src/hooks/useProjectData.ts
/**
* Project Data Hook
* Custom hook for dynamically loading project data based on current language
*/
import { useTranslation } from 'react-i18next';
import { useState, useEffect } from 'react';
/**
* Loads project data dynamically based on project ID and current language
*
* @template T - The type of the project data object
* @param {string} projectId - The unique identifier of the project
* @returns {T | null} The project data object or null if loading fails
*/
export const useProjectData = <T = unknown>(projectId: string): T | null => {
const { i18n } = useTranslation();
const [projectData, setProjectData] = useState<T | null>(null);
+10
View File
@@ -1,8 +1,18 @@
/**
* Scroll Lock Hook
* Manages body scroll locking based on state and navigation transitions
*/
// hooks/useScrollLock.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useScrollContext } from '../components/ScrollContext';
/**
* Custom hook to lock/unlock page scrolling
* Automatically handles scroll restoration on route changes and cleanup on unmount
* @param lock - Whether to lock the page scroll
*/
export const useScrollLock = (lock: boolean) => {
const location = useLocation();
const { isTransitioning, lockScroll, unlockScroll } = useScrollContext();
+30 -1
View File
@@ -1,8 +1,37 @@
/**
* Scroll Tracking Hook
* Tracks user scroll depth and sends events to Google Tag Manager
*/
import { useEffect, useRef } from 'react';
import { trackScrollDepth } from '../services/gtm';
/**
* Hook to track scroll depth for GTM
* Custom hook to track scroll depth for GTM analytics
*
* Monitors user scroll behavior and triggers GTM events when the user reaches
* specific depth milestones (25%, 50%, 75%, 90%, 100%). Each milestone is
* tracked only once per page visit to avoid duplicate events.
*
* Features:
* - Debounced scroll event handling (100ms) for performance
* - Tracks depth milestones: 25%, 50%, 75%, 90%, 100%
* - Prevents duplicate tracking of the same milestone
* - Automatically resets tracking state on route changes
* - Checks initial scroll position on mount
*
* @example
* ```tsx
* function MyPage() {
* useScrollTracking();
* return <div>...</div>;
* }
* ```
*
* @remarks
* This hook should be used at the page/route level component to ensure
* accurate tracking across navigation. The tracking state is automatically
* reset when the URL pathname changes.
*/
export const useScrollTracking = () => {
const trackedDepths = useRef(new Set<number>());
+51 -1
View File
@@ -1,21 +1,42 @@
/**
* Blog Post Management Service
* Zentrale Verwaltung aller Blog-Beiträge mit automatischer Kategorie-Erkennung
*/
import fs from 'fs';
import path from 'path';
import { cache } from '@/utils/cache';
/**
* Blog Post Interface
* Definiert die Struktur eines Blog-Beitrags
*/
export interface BlogPost {
/** URL-freundlicher Slug */
slug: string;
/** Titel des Beitrags */
title: string;
/** Veröffentlichungsdatum (ISO 8601) */
date: string;
/** Kurze Zusammenfassung */
excerpt: string;
/** Pfad zum Cover-Bild */
coverImage: string;
/** Kategorie des Beitrags */
category?: string;
/** Schlagwörter/Tags */
tags?: string[];
/** Vollständiger Markdown-Inhalt */
content?: string;
}
// Verzeichnis der Blog-Beiträge
const BLOG_POSTS_DIR = path.join(process.cwd(), 'blog-posts');
// Category mapping based on filename patterns
/**
* Kategorie-Mapping basierend auf Dateinamen- und Inhaltsmustern
* Ordnet Keywords automatisch passenden Kategorien zu
*/
const categoryMap: Record<string, string> = {
'agentic': 'KI-Agenten',
'ai-agent': 'KI-Agenten',
@@ -59,6 +80,13 @@ const categoryMap: Record<string, string> = {
'home-assistant': 'Smart Home',
};
/**
* Erkennt automatisch die Kategorie eines Blog-Posts
* Analysiert Dateiname und Inhalt auf Keywords
* @param filename - Dateiname des Blog-Posts
* @param content - Inhalt des Blog-Posts
* @returns Die erkannte Kategorie oder 'Technologie' als Fallback
*/
function detectCategory(filename: string, content: string): string {
const lowerFilename = filename.toLowerCase();
const lowerContent = content.toLowerCase().slice(0, 500);
@@ -71,6 +99,13 @@ function detectCategory(filename: string, content: string): string {
return 'Technologie';
}
/**
* Parst einen Markdown-Blog-Post und extrahiert alle Metadaten
* Extrahiert Titel, Meta-Description, Keywords, Kategorie und generiert Slug
* @param filename - Dateiname des Markdown-Posts
* @param content - Vollständiger Markdown-Inhalt
* @returns BlogPost-Objekt oder null bei Fehler
*/
function parseMarkdownPost(filename: string, content: string): BlogPost | null {
try {
// Extract title from first H1
@@ -130,6 +165,11 @@ function parseMarkdownPost(filename: string, content: string): BlogPost | null {
}
}
/**
* Lädt alle Blog-Posts aus dem Dateisystem
* Posts werden nach Datum absteigend sortiert (neueste zuerst)
* @returns Array aller Blog-Posts
*/
export function getAllBlogPosts(): BlogPost[] {
const cacheKey = 'blog:all-posts';
@@ -174,11 +214,21 @@ export function getAllBlogPosts(): BlogPost[] {
return sortedPosts;
}
/**
* Sucht einen Blog-Post anhand seines Slugs
* @param slug - Der URL-Slug des gesuchten Posts
* @returns Der gefundene Blog-Post oder null
*/
export function getBlogPostBySlug(slug: string): BlogPost | null {
const posts = getAllBlogPosts();
return posts.find(post => post.slug === slug) || null;
}
/**
* Gibt alle verfügbaren Blog-Post Slugs zurück
* Nützlich für statische Seitengenerierung
* @returns Array aller Post-Slugs
*/
export function getBlogPostSlugs(): string[] {
return getAllBlogPosts().map(post => post.slug);
}
+13
View File
@@ -1,5 +1,14 @@
/**
* Markdown Rendering Utility
* Converts markdown text to styled React components
*/
import React from 'react';
/**
* Parse inline markdown syntax (code, bold, italic, links)
* Converts inline markdown elements within a text string to React components
*/
function parseInlineMarkdown(text: string): React.ReactNode {
const parts: React.ReactNode[] = [];
let remaining = text;
@@ -63,6 +72,10 @@ function parseInlineMarkdown(text: string): React.ReactNode {
return parts.length === 1 ? parts[0] : <>{parts}</>;
}
/**
* Render markdown content to React components
* Supports headers, lists, code blocks, tables, blockquotes, and inline formatting
*/
export function renderMarkdown(content: string): React.ReactNode {
const lines = content.split('\n');
const elements: React.ReactNode[] = [];
+9
View File
@@ -1,5 +1,14 @@
/**
* Supabase Browser Client
* Client-side Supabase client for authentication and database access
*/
import { createBrowserClient } from '@supabase/ssr';
/**
* Creates a Supabase browser client instance
* @returns Supabase client configured with environment variables
*/
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
+11
View File
@@ -1,6 +1,17 @@
/**
* Supabase Server Client Utility
* Creates server-side Supabase clients with cookie handling for SSR
*/
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
/**
* Creates a Supabase client for server-side usage
* Handles cookie management for authentication in Next.js server components
*
* @returns {Promise} Supabase server client instance
*/
export async function createClient() {
const cookieStore = await cookies();
+38 -7
View File
@@ -1,9 +1,16 @@
/**
* Analytics Service
* Zentrale Verwaltung von Google Analytics, Facebook Pixel und Cookie-basiertem Tracking
*/
import ReactGA from 'react-ga4';
const TRACKING_ID = 'G-N0PZBL7X18';
const FB_PIXEL_ID = 'XXXXXXXXXXXXXXXXX'; // Ersetze mit deiner Facebook Pixel ID
// Prüft, ob eine bestimmte Cookie-Kategorie aktiviert ist
/**
* Prüft, ob eine bestimmte Cookie-Kategorie aktiviert ist
*/
export const isCookieCategoryEnabled = (category: string): boolean => {
try {
const cookieSettings = localStorage.getItem('cookieSettings');
@@ -17,7 +24,9 @@ export const isCookieCategoryEnabled = (category: string): boolean => {
}
};
// Entfernt alle Google Analytics Cookies
/**
* Entfernt alle Google Analytics Cookies
*/
const removeGACookies = (): void => {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
@@ -33,7 +42,9 @@ const removeGACookies = (): void => {
}
};
// Google Analytics Initialisierung - mit strikter Zustimmungsprüfung
/**
* Initialisiert Google Analytics mit strikter Zustimmungsprüfung
*/
export const initGA = (): void => {
if (!isCookieCategoryEnabled('analytics')) {
// Entferne bestehende GA-Cookies, falls keine Zustimmung
@@ -49,6 +60,9 @@ export const initGA = (): void => {
});
};
/**
* Loggt einen Seitenaufruf in Google Analytics
*/
export const logPageView = (): void => {
if (!isCookieCategoryEnabled('analytics')) {
return;
@@ -61,6 +75,9 @@ export const logPageView = (): void => {
});
};
/**
* Trackt ein benutzerdefiniertes Event
*/
export const trackEvent = (category: string, action: string, label?: string): void => {
if (!isCookieCategoryEnabled('analytics')) {
return;
@@ -73,6 +90,9 @@ export const trackEvent = (category: string, action: string, label?: string): vo
});
};
/**
* Trackt eine Conversion
*/
export const trackConversion = (value?: number): void => {
if (!isCookieCategoryEnabled('analytics')) {
return;
@@ -85,6 +105,9 @@ export const trackConversion = (value?: number): void => {
});
};
/**
* Trackt Performance-Timing
*/
export const trackTiming = (category: string, variable: string, value: number): void => {
if (!isCookieCategoryEnabled('analytics')) {
return;
@@ -98,7 +121,9 @@ export const trackTiming = (category: string, variable: string, value: number):
});
};
// Facebook Pixel Initialisierung - nur mit Zustimmung
/**
* Initialisiert Facebook Pixel nur mit Zustimmung
*/
export const initFBPixel = (): void => {
if (!isCookieCategoryEnabled('marketing')) {
return;
@@ -122,7 +147,9 @@ export const initFBPixel = (): void => {
}
};
// Facebook Event tracking
/**
* Trackt Facebook Pixel Events
*/
export const trackFBEvent = (event: string, params?: Record<string, unknown>): void => {
if (!isCookieCategoryEnabled('marketing') || typeof window === 'undefined' || !window.fbq) {
return;
@@ -131,7 +158,9 @@ export const trackFBEvent = (event: string, params?: Record<string, unknown>): v
window.fbq('track', event, params);
};
// Google Fonts Handling - nur mit funktionaler Cookie-Zustimmung
/**
* Initialisiert Google Fonts nur mit funktionaler Cookie-Zustimmung
*/
export const initGoogleFonts = (): void => {
if (!isCookieCategoryEnabled('functional')) {
// Entferne bestehende Google Fonts
@@ -151,7 +180,9 @@ export const initGoogleFonts = (): void => {
// Code hier zur lokalen Einbindung der Schriftarten
};
// Funktionale Cookies initialisieren
/**
* Initialisiert funktionale Cookies und Services
*/
export const initFunctionalCookies = (): void => {
if (!isCookieCategoryEnabled('functional')) {
return;
+16
View File
@@ -1,9 +1,25 @@
/**
* API Utility Functions
* Provides enhanced fetch functionality with timeout support
*/
import { handleError, createAPIError } from './errorHandling';
/**
* Extended request options with timeout support
*/
interface RequestOptions extends RequestInit {
/** Request timeout in milliseconds (default: 8000ms) */
timeout?: number;
}
/**
* Fetch with automatic timeout and error handling
* @param resource - URL or resource to fetch
* @param options - Request options including optional timeout
* @returns Promise resolving to the fetch Response
* @throws {Error} On timeout, network error, or HTTP error status
*/
export async function fetchWithTimeout(
resource: string,
options: RequestOptions = {}
+18 -2
View File
@@ -1,17 +1,33 @@
/**
* Authentication Utility Functions
* Handles user authentication checks and sign out operations
*/
import { supabase } from './supabaseClient';
import { NavigateFunction } from 'react-router-dom';
/**
* Check if user is authenticated
* Verifies the current session and redirects to login if not authenticated
* @param {NavigateFunction} navigate - React Router navigate function for redirects
* @returns {Promise<boolean>} True if authenticated, false otherwise
*/
export const checkAuth = async (navigate: NavigateFunction) => {
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
navigate('/login');
return false;
}
return true;
};
/**
* Sign out current user
* Terminates the user session and redirects to login page
* @param {NavigateFunction} navigate - React Router navigate function for redirects
*/
export const signOut = async (navigate: NavigateFunction) => {
await supabase.auth.signOut();
navigate('/login');
+53 -1
View File
@@ -1,3 +1,11 @@
/**
* In-Memory Cache Utility
* Provides simple key-value caching with TTL (Time To Live) support
*/
/**
* Cache item structure with value, timestamp, and TTL
*/
type CacheItem<T> = {
value: T;
timestamp: number;
@@ -5,17 +13,34 @@ type CacheItem<T> = {
lastAccessed: number;
};
/**
* In-memory cache implementation with automatic expiration
*/
class Cache {
private storage: Map<string, CacheItem<any>>;
private readonly defaultTTL: number;
private readonly maxSize?: number;
<<<<<<< HEAD
/**
* Create a new cache instance
* @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
*/
constructor(defaultTTL = 5 * 60 * 1000) { // 5 minutes default TTL
=======
constructor(defaultTTL = 5 * 60 * 1000, maxSize?: number) { // 5 minutes default TTL
>>>>>>> origin/master
this.storage = new Map();
this.defaultTTL = defaultTTL;
this.maxSize = maxSize;
}
/**
* Store a value in cache with optional TTL
* @param key - Cache key
* @param value - Value to cache
* @param ttl - Time-to-live in milliseconds (uses default if not specified)
*/
set<T>(key: string, value: T, ttl = this.defaultTTL): void {
const now = Date.now();
@@ -32,6 +57,14 @@ class Cache {
});
}
<<<<<<< HEAD
/**
* Retrieve a value from cache
* Returns null if key doesn't exist or has expired
* @param key - Cache key
* @returns Cached value or null
*/
=======
private evictLRU(): void {
let lruKey: string | null = null;
let lruTime = Infinity;
@@ -48,6 +81,7 @@ class Cache {
}
}
>>>>>>> origin/master
get<T>(key: string): T | null {
const item = this.storage.get(key);
@@ -58,23 +92,41 @@ class Cache {
return null;
}
<<<<<<< HEAD
=======
// Update last accessed time for LRU tracking
item.lastAccessed = Date.now();
>>>>>>> origin/master
return item.value;
}
/**
* Check if a key exists and is not expired
* @param key - Cache key
* @returns True if key exists and is valid
*/
has(key: string): boolean {
return this.get(key) !== null;
}
/**
* Remove a specific key from cache
* @param key - Cache key to delete
*/
delete(key: string): void {
this.storage.delete(key);
}
/**
* Clear all cached items
*/
clear(): void {
this.storage.clear();
}
}
export const cache = new Cache();
/**
* Shared cache instance with 5-minute default TTL
*/
export const cache = new Cache();
+16 -2
View File
@@ -1,6 +1,12 @@
// src/utils/constants.ts
// Zentrale Konstanten für die gesamte Website
/**
* Application Constants
* Zentrale Konstanten für die gesamte Website
*/
/**
* Contact information
* Kontaktinformationen für alle Kontaktpunkte auf der Website
*/
export const CONTACT = {
email: "info@damjan-savic.com",
phone: "+49 175 695 0979",
@@ -14,12 +20,20 @@ export const CONTACT = {
}
} as const;
/**
* Current professional role
* Aktuelle berufliche Position und Unternehmen
*/
export const CURRENT_ROLE = {
company: "Everlast Consulting GmbH",
position: "Process Automation Specialist",
since: "2024-12"
} as const;
/**
* Profile information
* Profil- und persönliche Informationen für die gesamte Website
*/
export const PROFILE = {
name: "Damjan Savić",
title: "Fullstack Developer",
+14 -3
View File
@@ -1,20 +1,31 @@
/**
* CSRF Token Utility
* Verwaltung von Cross-Site Request Forgery Schutz-Tokens
*/
import { v4 as uuidv4 } from 'uuid';
// CSRF token storage
let csrfToken: string | null = null;
// Generate a new CSRF token
/**
* Generate a new CSRF token
*/
export const generateCsrfToken = (): string => {
csrfToken = uuidv4();
return csrfToken;
};
// Validate a CSRF token
/**
* Validate a CSRF token
*/
export const validateCsrfToken = (token: string): boolean => {
return token === csrfToken;
};
// Get the current CSRF token
/**
* Get the current CSRF token
*/
export const getCsrfToken = (): string => {
if (!csrfToken) {
return generateCsrfToken();
+17
View File
@@ -1,5 +1,13 @@
/**
* Error Handling Utilities
* Centralized error handling and custom error types
*/
import { trackEvent } from './analytics';
/**
* Custom application error class with error codes, severity levels, and metadata
*/
export class AppError extends Error {
constructor(
message: string,
@@ -12,6 +20,9 @@ export class AppError extends Error {
}
}
/**
* Standard error codes used throughout the application
*/
export const errorCodes = {
NETWORK_ERROR: 'ERR_NETWORK',
API_ERROR: 'ERR_API',
@@ -20,6 +31,9 @@ export const errorCodes = {
UNKNOWN_ERROR: 'ERR_UNKNOWN'
} as const;
/**
* Handle and normalize errors with tracking and logging
*/
export const handleError = (error: unknown, context: string) => {
let appError: AppError;
@@ -52,6 +66,9 @@ export const handleError = (error: unknown, context: string) => {
return appError;
};
/**
* Create an API error with HTTP status code
*/
export const createAPIError = (status: number, message: string) => {
return new AppError(
message,
+18 -5
View File
@@ -1,4 +1,13 @@
// Font loading utility to ensure fonts are loaded before showing content
/**
* Font Loading Utility
* Ensures fonts are loaded before showing content to prevent FOUT/FOIT
*/
/**
* Preload fonts asynchronously
* Uses the Font Loading API to ensure Inter font is loaded
* Falls back to system fonts on error
*/
export const preloadFonts = async () => {
if ('fonts' in document) {
try {
@@ -12,14 +21,18 @@ export const preloadFonts = async () => {
}
};
// Call this function as early as possible
/**
* Initialize font loading process
* Should be called as early as possible in the application lifecycle
* Adds loading state and fallback timeout to prevent indefinite loading
*/
export const initializeFonts = () => {
// Add loading class immediately
document.documentElement.classList.add('fonts-loading');
// Start font loading
preloadFonts();
// Remove loading class after a timeout to prevent indefinite loading state
setTimeout(() => {
document.documentElement.classList.remove('fonts-loading');
@@ -27,4 +40,4 @@ export const initializeFonts = () => {
document.documentElement.classList.add('fonts-fallback');
}
}, 3000);
};
};
+71
View File
@@ -0,0 +1,71 @@
/**
* Rate limit entry tracking request count and timestamp
*/
interface RateLimitEntry {
count: number;
timestamp: number;
}
const WINDOW_SIZE_MS = 3600000; // 1 hour
const MAX_REQUESTS = 5; // Maximum requests per hour
/**
* Rate Limiter for API request throttling
* Manages rate limiting based on IP addresses with sliding window
*/
class RateLimiter {
private cache: Map<string, RateLimitEntry>;
constructor() {
this.cache = new Map();
}
/**
* Check if an IP address is currently rate limited
*/
isRateLimited(ip: string): boolean {
const now = Date.now();
const entry = this.cache.get(ip);
if (!entry) {
this.cache.set(ip, { count: 1, timestamp: now });
return false;
}
if (now - entry.timestamp > WINDOW_SIZE_MS) {
this.cache.set(ip, { count: 1, timestamp: now });
return false;
}
if (entry.count >= MAX_REQUESTS) {
return true;
}
entry.count++;
return false;
}
/**
* Get remaining attempts for an IP address
*/
getRemainingAttempts(ip: string): number {
const entry = this.cache.get(ip);
if (!entry) {
return MAX_REQUESTS;
}
return Math.max(0, MAX_REQUESTS - entry.count);
}
/**
* Get time in milliseconds until rate limit resets for an IP
*/
getTimeToReset(ip: string): number {
const entry = this.cache.get(ip);
if (!entry) {
return 0;
}
return Math.max(0, WINDOW_SIZE_MS - (Date.now() - entry.timestamp));
}
}
export const rateLimiter = new RateLimiter();
+13
View File
@@ -1,3 +1,12 @@
/**
* Service Worker Registration Utility
* Handles registration and unregistration of service workers for PWA functionality
*/
/**
* Registers the service worker
* Automatically registers the service worker after the window load event
*/
export function register() {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
@@ -14,6 +23,10 @@ export function register() {
}
}
/**
* Unregisters the service worker
* Removes the currently registered service worker
*/
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
+24
View File
@@ -1,6 +1,30 @@
/**
* Supabase Client Configuration
* Zentrale Konfiguration für die Supabase-Verbindung
*/
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://mxadgucxhmstlzsbgmoz.supabase.co';
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im14YWRndWN4aG1zdGx6c2JnbW96Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkwMDgwNjIsImV4cCI6MjA1NDU4NDA2Mn0.MmFqEm7OfwzOlSegLYl9YWLCmIp8YajzK3Aozubn66Q';
/**
* Supabase Client Instanz
*
* Vorkonfigurierter Supabase-Client für den Zugriff auf die Datenbank.
* Verwendet die anonyme (anon) Rolle für öffentliche Zugriffe.
*
* @example
* // Daten aus einer Tabelle abrufen
* const { data, error } = await supabase
* .from('table_name')
* .select('*');
*
* @example
* // Authentifizierung
* const { user, error } = await supabase.auth.signIn({
* email: 'user@example.com',
* password: 'password'
* });
*/
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
+17 -1
View File
@@ -1,5 +1,13 @@
/**
* Web Vitals Reporting Service
* Tracks and reports Core Web Vitals metrics to Google Analytics
*/
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
/**
* Web Vitals metric data structure
*/
interface Metric {
name: string;
value: number;
@@ -8,6 +16,9 @@ interface Metric {
id: string;
}
/**
* Send Web Vitals metric to Google Analytics and dispatch custom event
*/
function sendToAnalytics(metric: Metric) {
const body = JSON.stringify({
name: metric.name,
@@ -45,6 +56,9 @@ function sendToAnalytics(metric: Metric) {
// }
}
/**
* Initialize Web Vitals reporting for all Core Web Vitals metrics
*/
export function reportWebVitals() {
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
@@ -53,7 +67,9 @@ export function reportWebVitals() {
onTTFB(sendToAnalytics);
}
// Helper function to get Web Vitals score
/**
* Calculate Web Vitals rating based on metric thresholds
*/
export function getWebVitalsScore(metric: Metric): string {
const thresholds = {
LCP: { good: 2500, poor: 4000 },