Skip to content
Open
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
28 changes: 10 additions & 18 deletions apps/extension-wallet/eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,12 @@ module.exports = [
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsparser,
globals: {
...globals.browser,
...globals.vitest,
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
globals: {
...globals.browser,
Expand All @@ -33,34 +25,34 @@ module.exports = [
},
plugins: {
'@typescript-eslint': tseslint,
'react': react,
react,
'react-hooks': reactHooks,
},
rules: {
...tseslint.configs.recommended.rules,
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'react/react-in-jsx-scope': 'off', // Not needed in React 18+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'no-undef': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
settings: {
react: {
version: 'detect',
},
},
},
rules: {
...tseslint.configs.recommended.rules,
'no-undef': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
},
},
{
files: ['**/__tests__/**/*.{ts,tsx}', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'],
languageOptions: {
globals: {
...globals.jest,
...globals.vitest,
vi: 'readonly',
},
},
Expand Down
2 changes: 1 addition & 1 deletion apps/extension-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"react-dom": "^18.3.1",
"react-error-boundary": "^6.1.1"
},
"devDependencies": {
"devDependencies": {
"@eslint/js": "^9.0.0",
"@testing-library/jest-dom": "^6.1.0",
"@testing-library/react": "^14.0.0",
Expand Down
294 changes: 3 additions & 291 deletions apps/extension-wallet/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,293 +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,
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 { 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>
);
}

/**
* 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 function with error handling (example utility export usage)
void 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();
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.


// 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;
7 changes: 1 addition & 6 deletions apps/extension-wallet/src/components/PaymentQRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as React from 'react';
import { QRCodeSVG } from 'qrcode.react';
import { cn } from '@ancore/ui-kit';

Expand All @@ -22,11 +21,7 @@ export interface PaymentQRCodeProps {
* Uses qrcode.react under the hood with a styled card border so it fits
* the extension-wallet design system.
*/
export function PaymentQRCode({
value,
size = 220,
className,
}: PaymentQRCodeProps) {
export function PaymentQRCode({ value, size = 220, className }: PaymentQRCodeProps) {
return (
<div
className={cn(
Expand Down
Loading
Loading