diff --git a/src/supabaseClient.js b/src/supabaseClient.js index 86cb444..668c4cd 100644 --- a/src/supabaseClient.js +++ b/src/supabaseClient.js @@ -3,4 +3,38 @@ import { createClient } from '@supabase/supabase-js'; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; -export const supabase = createClient(supabaseUrl, supabaseAnonKey); \ No newline at end of file +// Validate environment variables +if (!supabaseUrl || !supabaseAnonKey) { + console.error('Missing Supabase environment variables'); + throw new Error( + 'Supabase configuration error: VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY must be defined in environment variables' + ); +} + +// Validate URL format +try { + new URL(supabaseUrl); +} catch (error) { + throw new Error(`Invalid Supabase URL format: ${supabaseUrl}`); +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); + +// Helper function to handle Supabase errors +export const handleSupabaseError = (error, context = '') => { + console.error(`Supabase error${context ? ` in ${context}` : ''}:`, error); + + if (error.message) { + return { + success: false, + error: error.message, + context + }; + } + + return { + success: false, + error: 'An unexpected error occurred', + context + }; +}; diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js new file mode 100644 index 0000000..e94b133 --- /dev/null +++ b/src/utils/errorHandler.js @@ -0,0 +1,88 @@ +/** + * Centralized error handling utilities + */ + +/** + * Error types for categorization + */ +export const ErrorTypes = { + VALIDATION: 'VALIDATION_ERROR', + NETWORK: 'NETWORK_ERROR', + AUTH: 'AUTHENTICATION_ERROR', + DATABASE: 'DATABASE_ERROR', + UNKNOWN: 'UNKNOWN_ERROR' +}; + +/** + * Custom error class for application errors + */ +export class AppError extends Error { + constructor(message, type = ErrorTypes.UNKNOWN, details = {}) { + super(message); + this.name = 'AppError'; + this.type = type; + this.details = details; + this.timestamp = new Date().toISOString(); + } +} + +/** + * Logs error with context + * @param {Error} error - The error to log + * @param {string} context - Context where error occurred + */ +export const logError = (error, context = '') => { + const errorInfo = { + message: error.message, + type: error.type || ErrorTypes.UNKNOWN, + context, + timestamp: new Date().toISOString(), + stack: error.stack + }; + + console.error('Application Error:', errorInfo); + + // In production, you might want to send this to an error tracking service + // Example: sendToErrorTracker(errorInfo); +}; + +/** + * Creates a user-friendly error message + * @param {Error} error - The error object + * @returns {string} - User-friendly message + */ +export const getUserFriendlyMessage = (error) => { + if (error instanceof AppError) { + switch (error.type) { + case ErrorTypes.VALIDATION: + return error.message; + case ErrorTypes.NETWORK: + return 'Network error. Please check your connection and try again.'; + case ErrorTypes.AUTH: + return 'Authentication failed. Please log in again.'; + case ErrorTypes.DATABASE: + return 'Database error. Please try again later.'; + default: + return 'An unexpected error occurred. Please try again.'; + } + } + + return error.message || 'An unexpected error occurred.'; +}; + +/** + * Error boundary wrapper for async operations + * @param {Function} fn - Function to wrap + * @param {string} context - Context for error logging + * @returns {Function} - Wrapped function + */ +export const withErrorBoundary = (fn, context) => { + return async (...args) => { + try { + return await fn(...args); + } catch (error) { + logError(error, context); + throw error; + } + }; +}; diff --git a/src/utils/validators.js b/src/utils/validators.js new file mode 100644 index 0000000..aba44ec --- /dev/null +++ b/src/utils/validators.js @@ -0,0 +1,119 @@ +/** + * Utility functions for input validation and error handling + */ + +/** + * Validates if a value is not null or undefined + * @param {*} value - The value to validate + * @param {string} fieldName - Name of the field for error messages + * @returns {boolean} - True if valid + * @throws {Error} - If validation fails + */ +export const validateRequired = (value, fieldName = 'Field') => { + if (value === null || value === undefined) { + throw new Error(`${fieldName} is required`); + } + return true; +}; + +/** + * Validates if a string is not empty + * @param {string} value - The string to validate + * @param {string} fieldName - Name of the field for error messages + * @returns {boolean} - True if valid + * @throws {Error} - If validation fails + */ +export const validateNonEmptyString = (value, fieldName = 'Field') => { + validateRequired(value, fieldName); + + if (typeof value !== 'string') { + throw new Error(`${fieldName} must be a string`); + } + + if (value.trim().length === 0) { + throw new Error(`${fieldName} cannot be empty`); + } + + return true; +}; + +/** + * Validates email format + * @param {string} email - The email to validate + * @returns {boolean} - True if valid + * @throws {Error} - If validation fails + */ +export const validateEmail = (email) => { + validateNonEmptyString(email, 'Email'); + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + throw new Error('Invalid email format'); + } + + return true; +}; + +/** + * Validates if a value is a positive number + * @param {number} value - The number to validate + * @param {string} fieldName - Name of the field for error messages + * @returns {boolean} - True if valid + * @throws {Error} - If validation fails + */ +export const validatePositiveNumber = (value, fieldName = 'Value') => { + validateRequired(value, fieldName); + + if (typeof value !== 'number' || isNaN(value)) { + throw new Error(`${fieldName} must be a valid number`); + } + + if (value <= 0) { + throw new Error(`${fieldName} must be positive`); + } + + return true; +}; + +/** + * Safely executes an async function with error handling + * @param {Function} asyncFn - The async function to execute + * @param {string} context - Context for error messages + * @returns {Promise<{success: boolean, data?: any, error?: string}>} + */ +export const safeAsync = async (asyncFn, context = 'Operation') => { + try { + const result = await asyncFn(); + return { + success: true, + data: result + }; + } catch (error) { + console.error(`Error in ${context}:`, error); + return { + success: false, + error: error.message || 'An unexpected error occurred' + }; + } +}; + +/** + * Validates an array is not empty + * @param {Array} arr - The array to validate + * @param {string} fieldName - Name of the field for error messages + * @returns {boolean} - True if valid + * @throws {Error} - If validation fails + */ +export const validateNonEmptyArray = (arr, fieldName = 'Array') => { + validateRequired(arr, fieldName); + + if (!Array.isArray(arr)) { + throw new Error(`${fieldName} must be an array`); + } + + if (arr.length === 0) { + throw new Error(`${fieldName} cannot be empty`); + } + + return true; +};