Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion apps/extension-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@
"vite": "^5.0.0",
"vitest": "^1.0.0"
}
}
}
2 changes: 1 addition & 1 deletion apps/extension-wallet/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
Expand Down
304 changes: 3 additions & 301 deletions apps/extension-wallet/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,303 +1,5 @@
/**
* App Component Example
*
* Demonstrates how to use ErrorBoundary and error-handler in a React application.
* This example shows:
* 1. Wrapping the app with ErrorBoundary
* 2. Using error-handler in async functions
* 3. Implementing retry functionality
* 4. Handling different error categories
*/
import { SettingsScreen } from './screens/Settings/SettingsScreen';

import { useState, useEffect, useCallback } from 'react';
import { ErrorBoundary, useErrorHandler, handleError, ErrorCategory, withErrorHandling, createRetryable } from './errors';

/**
* Sample data type
*/
interface UserData {
id: string;
name: string;
balance: string;
}

/**
* Example component that fetches data - demonstrates async error handling
* Uses the error-handler to classify and log errors
*/
function DataFetcher(): JSX.Element {
const [data, setData] = useState<UserData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);

// Use the error handler hook for manual error dispatching
const { dispatch, reset } = useErrorHandler();

const fetchData = useCallback(async () => {
setLoading(true);
setError(null);

try {
// Simulate a network request that might fail
const response = await fetch('/api/user');

if (!response.ok) {
// Use the global error handler to classify the error
const errorInfo = handleError(new Error(`HTTP ${response.status}: ${response.statusText}`), 'fetchUserData');
throw new Error(errorInfo.message);
}

const userData = await response.json();
setData(userData);
} catch (err) {
const handledError = handleError(err, 'fetchUserData');

// Log the error (handled by error-handler internally)
console.log('Error category:', handledError.category);
console.log('Recoverable:', handledError.recoverable);

setError(handledError.originalError as Error);
} finally {
setLoading(false);
}
}, []);

// Initial fetch on mount
useEffect(() => {
fetchData();
}, [fetchData]);

if (error) {
return (
<div className="p-4 border border-red-300 rounded-lg bg-red-50">
<p className="text-red-800 mb-2">Error: {error.message}</p>
<div className="flex gap-2">
<button
onClick={fetchData}
className="px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700"
>
Retry
</button>
<button
onClick={reset}
className="px-3 py-1 border border-red-600 text-red-600 rounded hover:bg-red-50"
>
Reset
</button>
</div>
</div>
);
}

if (loading) {
return <div className="p-4">Loading...</div>;
}

return (
<div className="p-4">
<h3 className="font-bold">User Data</h3>
{data && (
<ul>
<li>ID: {data.id}</li>
<li>Name: {data.name}</li>
<li>Balance: {data.balance}</li>
</ul>
)}
<button
onClick={fetchData}
className="mt-2 px-3 py-1 bg-blue-600 text-white rounded"
>
Refresh
</button>
</div>
);
export function App() {
return <SettingsScreen />;
}
Comment on lines +1 to 5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if ErrorBoundary is used elsewhere to wrap the App component
rg -n "ErrorBoundary" apps/extension-wallet/src --type tsx --type ts -C 2

Repository: ancore-org/ancore

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for ErrorBoundary usage in the entire apps/extension-wallet/src directory
rg -n "ErrorBoundary" apps/extension-wallet/src -C 2

Repository: ancore-org/ancore

Length of output: 11113


🏁 Script executed:

#!/bin/bash
# Check the entry point files to see how the app is bootstrapped
echo "=== Checking for entry point files ==="
fd -t f "^(index|main)\.(tsx?|jsx?)$" apps/extension-wallet/src
echo ""
echo "=== Content of potential entry point files ==="
for file in $(fd -t f "^(index|main)\.(tsx?|jsx?)$" apps/extension-wallet/src); do
  echo "--- File: $file ---"
  cat -n "$file"
done

Repository: ancore-org/ancore

Length of output: 2248


🏁 Script executed:

#!/bin/bash
# Get more context about the PR changes - check git log or diff if available
echo "=== Checking git log for this file ==="
git log --oneline -n 10 apps/extension-wallet/src/App.tsx 2>/dev/null || echo "Git history not available"
echo ""
echo "=== Checking for ErrorBoundary imports in App.tsx and related files ==="
rg -n "import.*ErrorBoundary" apps/extension-wallet/src

Repository: ancore-org/ancore

Length of output: 362


Add ErrorBoundary at the app entry point.

The app has no error boundary protection at any level. While ErrorBoundary components exist in the codebase (apps/extension-wallet/src/errors/), neither App.tsx nor the actual entry point (main.tsx) uses them. Unhandled errors in SettingsScreen will crash the app without a user-friendly fallback UI.

Wrap the app with ErrorBoundary in main.tsx (around <SettingsScreen />):

Example fix for main.tsx
import { ErrorBoundary } from './errors';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ErrorBoundary>
      <div className="w-[360px] min-h-screen bg-background mx-auto shadow-xl">
        <SettingsScreen />
      </div>
    </ErrorBoundary>
  </React.StrictMode>
);

Additionally, this change (simplifying App.tsx) appears unrelated to the PR's stated objectives (XDR encoding/decoding utils). Consider splitting into a separate PR for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/extension-wallet/src/App.tsx` around lines 1 - 5, The app entry has no
ErrorBoundary so unhandled errors in SettingsScreen will crash the UI; modify
the application entry (main.tsx) to import and wrap the rendered tree (the
<SettingsScreen /> mount) with the existing ErrorBoundary (from './errors'),
placing ErrorBoundary around the div that contains SettingsScreen, and keep
App.tsx as a simple component export (it can remain as returning <SettingsScreen
/>) or remove duplicate mounting logic—ensure you import ErrorBoundary, wrap the
root render (not just App.tsx), and provide the fallback UI via the existing
ErrorBoundary component.


/**
* Example component using withErrorHandling HOC
* Wraps an async function with automatic error handling
*/
async function fetchUserBalance(userId: string): Promise<string> {
// Simulate network call
const response = await fetch(`/api/balance/${userId}`);

if (!response.ok) {
throw new Error('Failed to fetch balance');
}

const data = await response.json();
return data.balance;
}

// Wrap the function with error handling - using type assertion for demo
const fetchUserBalanceWithErrorHandling = withErrorHandling(fetchUserBalance as any, 'fetchUserBalance');

/**
* Example component using createRetryable
* Creates a function that automatically retries on failure
*/
async function submitTransaction(txData: object): Promise<{ txHash: string }> {
// Simulate transaction submission
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(txData),
});

if (!response.ok) {
throw new Error('Transaction failed');
}

return response.json();
}

// Create a retryable version that retries up to 3 times
const submitTransactionWithRetry = createRetryable(submitTransaction as any, 3, 1000);

/**
* Transaction component - demonstrates retry functionality
*/
function TransactionComponent(): JSX.Element {
const [status, setStatus] = useState<string>('idle');
const [txHash, setTxHash] = useState<string | null>(null);

const handleSubmit = async () => {
setStatus('submitting');

const result = await submitTransactionWithRetry({ amount: 100 }) as { txHash: string };

if ('txHash' in result) {
setTxHash(result.txHash);
setStatus('success');
} else {
setStatus('failed');
}
};

return (
<div className="p-4 border rounded-lg">
<h3 className="font-bold mb-2">Transaction</h3>
<p className="mb-2">Status: {status}</p>
{txHash && <p className="mb-2">Tx Hash: {txHash}</p>}
<button
onClick={handleSubmit}
disabled={status === 'submitting'}
className="px-3 py-1 bg-green-600 text-white rounded"
>
{status === 'submitting' ? 'Submitting...' : 'Submit Transaction'}
</button>
</div>
);
}

/**
* Main App component - wrapped with ErrorBoundary
* The ErrorBoundary will catch any rendering errors in children
*/
export function App(): JSX.Element {
// Callback for handling errors that escape component boundaries
const handleAppError = (error: Error, errorInfo: React.ErrorInfo) => {
console.error('App-level error:', error, errorInfo.componentStack);
};

// Callback for resetting app state
const handleReset = () => {
console.log('App reset requested');
};

return (
<div className="min-h-screen bg-gray-100 p-8">
<h1 className="text-3xl font-bold mb-8">Extension Wallet</h1>

{/* Wrap the entire app with ErrorBoundary */}
<ErrorBoundary
onError={handleAppError}
onReset={handleReset}
>
<div className="space-y-8">
{/* Example 1: Data fetching with manual error handling */}
<section className="bg-white p-4 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-4">Data Fetcher Example</h2>
<DataFetcher />
</section>

{/* Example 2: Transaction with retry */}
<section className="bg-white p-4 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-4">Transaction Example (with retry)</h2>
<TransactionComponent />
</section>

{/* Example 3: Direct error handler usage */}
<section className="bg-white p-4 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-4">Direct Error Handler Example</h2>
<DirectErrorExample />
</section>
</div>
</ErrorBoundary>
</div>
);
}

/**
* Component demonstrating direct use of error-handler
*/
function DirectErrorExample(): JSX.Element {
const [result, setResult] = useState<string | null>(null);

const testNetworkError = () => {
const errorInfo = handleError(new Error('ECONNREFUSED: Connection refused'), 'networkTest');
setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`);
};

const testValidationError = () => {
const errorInfo = handleError(new Error('validation failed: invalid address'), 'validationTest');
setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`);
};

const testContractError = () => {
const errorInfo = handleError(new Error('Contract: execution reverted'), 'contractTest');
setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`);
};

const testUnknownError = () => {
const errorInfo = handleError(new Error('Something unexpected'), 'unknownTest');
setResult(`Category: ${errorInfo.category}, Recoverable: ${errorInfo.recoverable}`);
};

return (
<div>
<div className="flex gap-2 mb-4">
<button
onClick={testNetworkError}
className="px-2 py-1 bg-gray-200 rounded text-sm"
>
Test Network Error
</button>
<button
onClick={testValidationError}
className="px-2 py-1 bg-gray-200 rounded text-sm"
>
Test Validation Error
</button>
<button
onClick={testContractError}
className="px-2 py-1 bg-gray-200 rounded text-sm"
>
Test Contract Error
</button>
<button
onClick={testUnknownError}
className="px-2 py-1 bg-gray-200 rounded text-sm"
>
Test Unknown Error
</button>
</div>
{result && (
<p className="p-2 bg-blue-50 rounded text-sm">
{result}
</p>
)}
</div>
);
}

export default App;
8 changes: 4 additions & 4 deletions apps/extension-wallet/src/components/SettingsGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export function SettingItem({
onClick={onClick}
>
{icon && (
<span className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${danger ? 'bg-destructive/10 text-destructive' : 'bg-primary/10 text-primary'}`}>
<span
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${danger ? 'bg-destructive/10 text-destructive' : 'bg-primary/10 text-primary'}`}
>
{icon}
</span>
)}
Expand All @@ -63,9 +65,7 @@ export function SettingItem({
</span>
{rightSlot ?? (
<>
{value !== undefined && (
<span className="text-xs text-muted-foreground">{value}</span>
)}
{value !== undefined && <span className="text-xs text-muted-foreground">{value}</span>}
{onClick && <ChevronRight className="h-4 w-4 text-muted-foreground/50 shrink-0" />}
</>
)}
Expand Down
21 changes: 8 additions & 13 deletions apps/extension-wallet/src/components/TransactionStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import * as React from 'react';
import { Badge, cn } from '@ancore/ui-kit';
import { AlertCircle, CheckCircle2, Clock3, XCircle } from 'lucide-react';

export type TransactionStatusKind =
| 'confirmed'
| 'pending'
| 'failed'
| 'cancelled';
export type TransactionStatusKind = 'confirmed' | 'pending' | 'failed' | 'cancelled';

const STATUS_STYLES: Record<
TransactionStatusKind,
Expand Down Expand Up @@ -38,22 +34,21 @@ const STATUS_STYLES: Record<
},
};

export interface TransactionStatusProps
extends React.HTMLAttributes<HTMLDivElement> {
export interface TransactionStatusProps extends React.HTMLAttributes<HTMLDivElement> {
status: TransactionStatusKind;
}

export function TransactionStatus({
status,
className,
...props
}: TransactionStatusProps) {
export function TransactionStatus({ status, className, ...props }: TransactionStatusProps) {
const { label, icon, className: statusClassName } = STATUS_STYLES[status];

return (
<Badge
variant="outline"
className={cn('inline-flex items-center gap-2 rounded-full px-3 py-1', statusClassName, className)}
className={cn(
'inline-flex items-center gap-2 rounded-full px-3 py-1',
statusClassName,
className
)}
{...props}
>
{icon}
Expand Down
Loading
Loading