First Commit - Portfolio Page

This commit is contained in:
2025-02-10 00:55:39 +01:00
commit bba0e331a8
239 changed files with 33355 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
import ReactGA from 'react-ga4';
const TRACKING_ID = 'G-N0PZBL7X18';
export const initGA = () => {
ReactGA.initialize(TRACKING_ID);
};
export const logPageView = () => {
ReactGA.send({ hitType: "pageview", page: window.location.pathname });
};
export const trackEvent = (category: string, action: string, label?: string) => {
ReactGA.event({
category,
action,
label
});
};
export const trackConversion = (value?: number) => {
ReactGA.event({
category: 'Conversion',
action: 'Complete',
value
});
};
export const trackTiming = (category: string, variable: string, value: number) => {
ReactGA.event({
category,
action: 'timing',
label: variable,
value
});
};
declare global {
interface Window {
initializeAnalytics: () => void;
}
}
// Make analytics initialization available globally for the cookie consent
window.initializeAnalytics = () => {
initGA();
logPageView();
};
+35
View File
@@ -0,0 +1,35 @@
import { handleError, createAPIError } from './errorHandling';
interface RequestOptions extends RequestInit {
timeout?: number;
}
export async function fetchWithTimeout(
resource: string,
options: RequestOptions = {}
): Promise<Response> {
const { timeout = 8000, ...fetchOptions } = options;
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(resource, {
...fetchOptions,
signal: controller.signal
});
if (!response.ok) {
throw createAPIError(
response.status,
`HTTP error! status: ${response.status}`
);
}
return response;
} catch (error) {
throw handleError(error, 'API Request');
} finally {
clearTimeout(id);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { supabase } from './supabaseClient';
import { NavigateFunction } from 'react-router-dom';
export const checkAuth = async (navigate: NavigateFunction) => {
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
navigate('/login');
return false;
}
return true;
};
export const signOut = async (navigate: NavigateFunction) => {
await supabase.auth.signOut();
navigate('/login');
};
+50
View File
@@ -0,0 +1,50 @@
type CacheItem<T> = {
value: T;
timestamp: number;
ttl: number;
};
class Cache {
private storage: Map<string, CacheItem<any>>;
private readonly defaultTTL: number;
constructor(defaultTTL = 5 * 60 * 1000) { // 5 minutes default TTL
this.storage = new Map();
this.defaultTTL = defaultTTL;
}
set<T>(key: string, value: T, ttl = this.defaultTTL): void {
this.storage.set(key, {
value,
timestamp: Date.now(),
ttl
});
}
get<T>(key: string): T | null {
const item = this.storage.get(key);
if (!item) return null;
if (Date.now() > item.timestamp + item.ttl) {
this.storage.delete(key);
return null;
}
return item.value;
}
has(key: string): boolean {
return this.get(key) !== null;
}
delete(key: string): void {
this.storage.delete(key);
}
clear(): void {
this.storage.clear();
}
}
export const cache = new Cache();
+23
View File
@@ -0,0 +1,23 @@
import { v4 as uuidv4 } from 'uuid';
// CSRF token storage
let csrfToken: string | null = null;
// Generate a new CSRF token
export const generateCsrfToken = (): string => {
csrfToken = uuidv4();
return csrfToken;
};
// Validate a CSRF token
export const validateCsrfToken = (token: string): boolean => {
return token === csrfToken;
};
// Get the current CSRF token
export const getCsrfToken = (): string => {
if (!csrfToken) {
return generateCsrfToken();
}
return csrfToken;
};
+62
View File
@@ -0,0 +1,62 @@
import { trackEvent } from './analytics';
export class AppError extends Error {
constructor(
message: string,
public code: string,
public severity: 'error' | 'warning' | 'info' = 'error',
public metadata?: Record<string, any>
) {
super(message);
this.name = 'AppError';
}
}
export const errorCodes = {
NETWORK_ERROR: 'ERR_NETWORK',
API_ERROR: 'ERR_API',
AUTH_ERROR: 'ERR_AUTH',
VALIDATION_ERROR: 'ERR_VALIDATION',
UNKNOWN_ERROR: 'ERR_UNKNOWN'
} as const;
export const handleError = (error: unknown, context: string) => {
let appError: AppError;
if (error instanceof AppError) {
appError = error;
} else if (error instanceof Error) {
appError = new AppError(
error.message,
errorCodes.UNKNOWN_ERROR,
'error',
{ originalError: error }
);
} else {
appError = new AppError(
'An unexpected error occurred',
errorCodes.UNKNOWN_ERROR,
'error',
{ originalError: error }
);
}
// Track error in analytics
trackEvent('Error', appError.code, `${context}: ${appError.message}`);
// Log error for debugging
if (process.env.NODE_ENV === 'development') {
console.error(`[${context}]`, appError);
}
return appError;
};
export const createAPIError = (status: number, message: string) => {
return new AppError(
message,
errorCodes.API_ERROR,
'error',
{ status }
);
};
+55
View File
@@ -0,0 +1,55 @@
interface RateLimitEntry {
count: number;
timestamp: number;
}
const WINDOW_SIZE_MS = 3600000; // 1 hour
const MAX_REQUESTS = 5; // Maximum requests per hour
class RateLimiter {
private cache: Map<string, RateLimitEntry>;
constructor() {
this.cache = new Map();
}
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;
}
getRemainingAttempts(ip: string): number {
const entry = this.cache.get(ip);
if (!entry) {
return MAX_REQUESTS;
}
return Math.max(0, MAX_REQUESTS - entry.count);
}
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();
+27
View File
@@ -0,0 +1,27 @@
export function register() {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = `${import.meta.env.BASE_URL}sw.js`;
navigator.serviceWorker
.register(swUrl)
.then((_registration) => {
console.log('ServiceWorker registration successful');
})
.catch((error) => {
console.error('ServiceWorker registration failed:', error);
});
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}
+6
View File
@@ -0,0 +1,6 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://mxadgucxhmstlzsbgmoz.supabase.co';
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im14YWRndWN4aG1zdGx6c2JnbW96Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkwMDgwNjIsImV4cCI6MjA1NDU4NDA2Mn0.MmFqEm7OfwzOlSegLYl9YWLCmIp8YajzK3Aozubn66Q';
export const supabase = createClient(supabaseUrl, supabaseAnonKey);