Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions payego_ui/src/components/AddBankForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { useBanks } from '../hooks/useBanks';
import { bankApi } from '../api/bank';
import { getErrorMessage } from '../utils/errorHandler';

const bankAccountSchema = z.object({
bankCode: z.string().min(1, 'Pick a bank, don’t be shy!'),
Expand Down Expand Up @@ -53,7 +54,7 @@ const AddBankForm: React.FC = () => {
});
navigate('/banks');
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to add bank account.');
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand All @@ -77,7 +78,7 @@ const AddBankForm: React.FC = () => {
setPendingData(data);
setShowConfirmation(true);
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to verify account.');
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -111,7 +112,7 @@ const AddBankForm: React.FC = () => {

setShowSuccess(true);
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to add bank account.');
setError(getErrorMessage(err));
setShowConfirmation(false);
} finally {
setLoading(false);
Expand Down
3 changes: 2 additions & 1 deletion payego_ui/src/components/BankList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ErrorBoundary from "./ErrorBoundary";
import { useUserBankAccounts } from "../hooks/useBanks";
import { bankApi } from "../api/bank";
import { useQueryClient } from "@tanstack/react-query";
import { getErrorMessage } from '../utils/errorHandler';

const BankList: React.FC = () => {
const { data: banks, isLoading } = useUserBankAccounts();
Expand Down Expand Up @@ -36,7 +37,7 @@ const BankList: React.FC = () => {
queryClient.invalidateQueries({ queryKey: ['user-banks'] });
setDeleteModal({ open: false, bankId: null, bankName: "" });
} catch (err) {
console.error("Failed to delete bank:", err);
console.error("Failed to delete bank:", getErrorMessage(err));
}
};

Expand Down
5 changes: 3 additions & 2 deletions payego_ui/src/components/ConvertForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useWallets } from "../hooks/useWallets";
import { transactionApi } from "../api/transactions";
import { Currency } from "@/types";
import client from "../api/client";
import { getErrorMessage } from '../utils/errorHandler';

const convertSchema = z.object({
amount: z.number().min(0.01).max(10000),
Expand Down Expand Up @@ -70,7 +71,7 @@ const ConvertForm: React.FC = () => {
});
setShowConfirmation(true);
} catch (err: any) {
setError(err.response?.data?.message || "Failed to fetch exchange rate.");
setError(getErrorMessage(err));
} finally {
setSubmitting(false);
}
Expand All @@ -96,7 +97,7 @@ const ConvertForm: React.FC = () => {

setShowSuccess(true);
} catch (err: any) {
setError(err.response?.data?.message || "Conversion failed.");
setError(getErrorMessage(err));
setShowConfirmation(false);
} finally {
setSubmitting(false);
Expand Down
5 changes: 3 additions & 2 deletions payego_ui/src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { useAuth } from '../contexts/AuthContext';
import { authApi } from '../api/auth';
import { getErrorMessage } from '../utils/errorHandler';

const loginSchema = z.object({
email: z.string().email('Please enter a valid email address'),
Expand Down Expand Up @@ -40,7 +41,7 @@ const LoginForm: React.FC = () => {
login(response.data.token);
navigate('/dashboard');
} catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please try again.');
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand All @@ -55,7 +56,7 @@ const LoginForm: React.FC = () => {
login(response.data.token);
navigate('/dashboard');
} catch (err: any) {
setError(err.response?.data?.message || 'Google login failed.');
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand Down
10 changes: 3 additions & 7 deletions payego_ui/src/components/PayPalPayment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { PayPalButtons } from '@paypal/react-paypal-js';
import { useQueryClient } from '@tanstack/react-query';
import client from '../api/client';
import { getErrorMessage } from '../utils/errorHandler';

interface PayPalPaymentProps {
paymentId: string;
Expand All @@ -15,12 +16,7 @@ const PayPalPayment: React.FC<PayPalPaymentProps> = ({ paymentId, transactionId,
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

const getErrorMessage = (message: string) => {
if (message.includes('INSTRUMENT_DECLINED')) {
return 'Your payment method was declined. Try a different card!';
}
return message || 'PayPal payment failed.';
};


return (
<div className="mt-6">
Expand Down Expand Up @@ -53,7 +49,7 @@ const PayPalPayment: React.FC<PayPalPaymentProps> = ({ paymentId, transactionId,
setError(getErrorMessage(response.data.error_message));
}
} catch (err: any) {
setError(getErrorMessage(err.response?.data?.error_message || 'Capture failed.'));
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand Down
3 changes: 2 additions & 1 deletion payego_ui/src/components/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { useAuth } from '../contexts/AuthContext';
import { authApi } from '../api/auth';
import { getErrorMessage } from '../utils/errorHandler';

const registerSchema = z.object({
email: z.string().email('Please enter a valid email address'),
Expand Down Expand Up @@ -68,7 +69,7 @@ const RegisterForm: React.FC = () => {
login(response.data.token);
setShowVerification(true);
} catch (err: any) {
setError(err.response?.data?.message || 'Registration failed.');
setError(getErrorMessage(err));
setLoading(false);
}
};
Expand Down
3 changes: 2 additions & 1 deletion payego_ui/src/components/StripePayment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { getErrorMessage } from '../utils/errorHandler';

interface StripePaymentProps {
clientSecret: string;
Expand Down Expand Up @@ -54,7 +55,7 @@ const StripePayment: React.FC<StripePaymentProps> = ({ clientSecret, transaction
}
} catch (err: any) {
console.error('Stripe confirmation error:', err);
setError(err.message || 'Payment failed');
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
Expand Down
3 changes: 2 additions & 1 deletion payego_ui/src/components/TopUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import PayPalPayment from './PayPalPayment';
import ErrorBoundary from './ErrorBoundary';
import { transactionApi } from '../api/transactions';
import { Currency } from '../types';
import { getErrorMessage } from '../utils/errorHandler';

const topUpSchema = z.object({
amount: z.number().min(1, 'Amount must be at least 1').max(10000, 'Amount must be at most 10,000'),
Expand Down Expand Up @@ -55,7 +56,7 @@ const TopUpForm: React.FC = () => {
window.location.assign(response.session_url);
}
} catch (err: any) {
setError(err.response?.data?.message || 'Payment initiation failed.');
setError(getErrorMessage(err));
} finally {
setSubmitting(false);
}
Expand Down
11 changes: 6 additions & 5 deletions payego_ui/src/components/TransferForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { transactionApi } from '../api/transactions';
import { usersApi } from '../api/users';
import client from '../api/client';
import { ResolvedUser } from '@/types';
import { getErrorMessage } from '../utils/errorHandler';

const transferSchema = z.discriminatedUnion('transferType', [
z.object({
Expand Down Expand Up @@ -66,7 +67,7 @@ const TransferForm: React.FC = () => {
const res = await client.get('/api/bank/resolve', { params: { bank_code: bankCode, account_number: accountNumber } });
setValue('accountName', res.data.account_name, { shouldValidate: true });
} catch (err) {
setError('Could not resolve account name.');
setError(getErrorMessage(err));
} finally {
setResolving(false);
}
Expand All @@ -87,7 +88,7 @@ const TransferForm: React.FC = () => {
setResolvedUser(user);
setShowConfirmation(true);
} catch (err) {
setError('User not found');
setError(getErrorMessage(err));
} finally {
setResolving(false);
}
Expand All @@ -106,7 +107,7 @@ const TransferForm: React.FC = () => {
setPendingExternalData(data);
setShowConfirmation(true);
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to fetch exchange rate.');
setError(getErrorMessage(err));
} finally {
setResolving(false);
}
Expand All @@ -131,7 +132,7 @@ const TransferForm: React.FC = () => {

navigate(`/success?tx=${result.id || result.transaction_id || ''}`);
} catch (err: any) {
setError(err.response?.data?.message || 'Transfer failed.');
setError(getErrorMessage(err));
setShowConfirmation(false);
} finally {
setSubmitting(false);
Expand Down Expand Up @@ -160,7 +161,7 @@ const TransferForm: React.FC = () => {
// Redirect to success page with transaction ID
navigate(`/success?tx=${result.id || result.transaction_id || ''}`);
} catch (err: any) {
setError(err.response?.data?.message || 'Transfer failed.');
setError(getErrorMessage(err));
setShowConfirmation(false);
} finally {
setSubmitting(false);
Expand Down
5 changes: 3 additions & 2 deletions payego_ui/src/components/WithdrawForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as z from 'zod';
import { useWallets } from '../hooks/useWallets';
import { useUserBankAccounts } from '../hooks/useBanks';
import { transactionApi } from '../api/transactions';
import { getErrorMessage } from '../utils/errorHandler';

const withdrawSchema = z.object({
amount: z.number().min(1, 'Minimum 1 required'),
Expand Down Expand Up @@ -48,7 +49,7 @@ const WithdrawForm: React.FC = () => {
setPendingData(data);
setShowConfirmation(true);
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to fetch exchange rate.');
setError(getErrorMessage(err));
} finally {
setSubmitting(false);
}
Expand Down Expand Up @@ -77,7 +78,7 @@ const WithdrawForm: React.FC = () => {

navigate(`/success?tx=${res.transaction_id}`);
} catch (err: any) {
setError(err.response?.data?.message || 'Withdrawal failed.');
setError(getErrorMessage(err));
setShowConfirmation(false);
} finally {
setSubmitting(false);
Expand Down
99 changes: 99 additions & 0 deletions payego_ui/src/utils/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Error handling utility for extracting user-friendly error messages from API responses
*/

export interface ApiErrorResponse {
message?: string;
error?: string;
errors?: Array<{ message?: string; field?: string } | string>;
}

/**
* Extracts a user-friendly error message from an error object
* @param error - The error object (typically from Axios)
* @returns A user-friendly error message
*/
export function getErrorMessage(error: any): string {
// Handle Axios errors with response
if (error.response) {
const data: ApiErrorResponse = error.response.data;

// Check for message field (most common)
if (data?.message) {
return data.message;
}

// Check for error field
if (data?.error) {
return data.error;
}

// Check for errors array (validation errors)
if (data?.errors && Array.isArray(data.errors)) {
const messages = data.errors.map(e => {
if (typeof e === 'string') return e;
if (e.message) return e.message;
return 'Validation error';
});
return messages.join(', ');
}

// Fallback to status-based messages
const status = error.response.status;
switch (status) {
case 400:
return 'Invalid request. Please check your input.';
case 401:
return 'Session expired. Please log in again.';
case 403:
return 'You don\'t have permission to perform this action.';
case 404:
return 'Resource not found.';
case 409:
return 'This action conflicts with existing data.';
case 422:
return 'Validation failed. Please check your input.';
case 429:
return 'Too many requests. Please try again later.';
case 500:
case 502:
case 503:
return 'Server error. Please try again later.';
default:
return error.response.statusText || 'Request failed';
}
}

// Handle network errors (no response received)
if (error.request) {
return 'Network error. Please check your internet connection.';
}

// Handle other errors
if (error.message) {
return error.message;
}

return 'An unexpected error occurred. Please try again.';
}

/**
* Checks if an error is a network error
*/
export function isNetworkError(error: any): boolean {
return error.request && !error.response;
}

/**
* Checks if an error is an authentication error
*/
export function isAuthError(error: any): boolean {
return error.response?.status === 401;
}

/**
* Checks if an error is a validation error
*/
export function isValidationError(error: any): boolean {
return error.response?.status === 400 || error.response?.status === 422;
}